Observe everything
You've been using the dashboard the entire course. Every time we placed an order, it logged what happened. Every time a step failed, it showed why. Every time the workflow waited, it told you what for.
This is the lesson where we stop treating it as scenery and use it on purpose.
Production workflows fail at 3am. The on-call engineer's first move is to open the dashboard, find the failing run, and figure out what step blew up and why. There's no SSHing into a box. There's no rummaging through logs. The dashboard is the debugger.
No new code today. Just a tour. Then you ship.
Outcome
Walk through every view in the Workflows dashboard with intent. Know where to look when a run fails, where step inputs and outputs live, and how to confirm a workflow survived a deploy.
Fast Track
- Open your project's Workflows tab in Vercel. Find a successful run, a failed run, and a still-running run.
- Click into each. Read the timeline, expand a step, read its inputs and outputs.
- Place an order. While it's mid-hook, ship a code change. Confirm the existing run completes on the new build.
Hands-on exercise
1. The run list.
The Workflows tab in your Vercel project lists every run. Each row has the workflow name, status, duration, and the time the run started.
If you don't see a healthy mix of completed, failed, and running runs, generate some:
- Place an order with
cardLast4: "4242"and walk it through. →completed - Place one with
cardLast4: "0000". →failed - Place one and don't touch the kitchen UI. →
runningfor 20 minutes, thencompleted(escalated) - Place one and click Mark ready halfway. →
runninguntil you continue clicking
2. The timeline view.
Click into a completed run. The timeline shows every step in order, every sleep, every hook.
processOrder completed 3m 12s
├─ chargeCard completed 217ms
│ input: { id: "0a4f…", cardLast4: "4242", ... }
│ output: { chargedAt: "2026-..." }
│ attempts: 1
├─ sendOrderConfirmation completed 389ms
│ input: { id: "0a4f…", email: "marge@...", ... }
│ output: undefined
│ attempts: 1
├─ acknowledgeKitchen completed 843ms
│ attempts:
│ 1. failed "Kitchen printer jammed. Retrying."
│ 2. completed
├─ kitchen hook (kitchen:0a4f…)
│ resumed after 2m 14s with { readyAt: "2026-..." }
├─ dispatchDelivery completed 213ms
├─ driver-pickup hook resumed after 35s
├─ markOutForDelivery completed 18ms
├─ driver-delivered hook resumed after 22s
├─ confirmDelivery completed 12ms
└─ sendReviewRequest completed 341ms
The runtime preserves everything: the inputs to each step (the full order object), the outputs (whatever the step returned), the attempt history (every retry, with the error message), and the hook payloads (what the kitchen/driver sent).
3. Inspect a retry.
Find a run where acknowledgeKitchen retried. Expand the step. You'll see attempts listed:
acknowledgeKitchen completed 843ms
attempts:
1. failed 208ms Error: Kitchen printer jammed. Retrying.
2. completed 211ms
Each attempt is a separate enqueue. The runtime records the failure, waits its backoff, and re-runs the step with the same inputs. The customer never noticed.
4. Inspect a fatal failure.
Find a run that failed because of the 0000 card. Expand chargeCard:
chargeCard failed 208ms
attempts:
1. failed FatalError: Card declined for order 0a4f...
One attempt. FatalError told the runtime not to retry. The workflow stopped immediately. Compare this to a run where you tried Resend with a deliberately bad email earlier. Same shape.
5. Inspect a hook.
Find a run that's still suspended on a hook. The timeline ends partway through with the hook line:
processOrder running
├─ chargeCard completed
├─ sendOrderConfirmation completed
├─ acknowledgeKitchen completed
└─ kitchen hook (kitchen:0a4f…)
suspended 1m 47s
It shows the token, how long the hook has been suspended, and that it's still waiting. No payload yet. Click into the kitchen ops UI and Mark ready. Refresh the dashboard. The hook line now shows resumed, and the workflow moves forward.
6. Survive a deploy.
Place an order and let it suspend on the kitchen hook.
Open workflows/steps/send-order-confirmation.ts. Change the email subject to add "v2" at the end. Commit. Push. Vercel deploys.
Click Mark ready in the kitchen UI to wake the suspended workflow.
The dashboard shows the workflow continuing on the new deploy. The sendOrderConfirmation step that already completed doesn't run again. The "v2" subject change applies only to future orders.
processOrder completed
├─ chargeCard completed (replayed from event log)
├─ sendOrderConfirmation completed (replayed from event log, "v2" not used)
├─ acknowledgeKitchen completed (replayed from event log)
├─ kitchen hook resumed (on new deploy)
├─ dispatchDelivery completed (executed on new deploy)
...
That's the durability promise. Mid-flight workflows survive deploys. Their original timeline is sacred.
Try It
Open a fresh tab and place ten orders. Use different combinations:
- Some with
4242, some with0000 - Some you walk through the kitchen and driver UIs
- Some you ghost the kitchen for the full timeout
- Some you redeploy mid-suspension
Then go to the Workflows tab and look at the run list. You should see a mix of completed, failed, and escalated runs. Pick three at random. For each, predict what the timeline will look like before you click in. Then click in and check.
If you can do this exercise comfortably, you can debug a Workflow SDK production incident.
Commit
Nothing to commit. This lesson is exploration.
Done-When
- You've walked through completed, failed, and running runs in the dashboard
- You've expanded a step and read its inputs, outputs, and attempt history
- You've inspected a hook in
suspendedandresumedstates - You've shipped a code change mid-workflow and watched the existing run finish on the new build
Solution
No code in this lesson. The skill is the lesson.
You started this course with setTimeout and a database table. You're ending it with await sleep("7 days") and a dashboard. Same problem, different vocabulary, an order of magnitude less code.
Go ship a workflow.
Was this helpful?