Vercel Logo

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

  1. Open your project's Workflows tab in Vercel. Find a successful run, a failed run, and a still-running run.
  2. Click into each. Read the timeline, expand a step, read its inputs and outputs.
  3. 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. → running for 20 minutes, then completed (escalated)
  • Place one and click Mark ready halfway. → running until 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 with 0000
  • 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.

What you can do now

You can write durable workflows that sequence steps, wait for real-world events with hooks, time out gracefully, retry on flakes, fail fast on unrecoverable errors, survive deploys, and observe every step in production. That's the entire vocabulary.

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 suspended and resumed states
  • 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?

supported.