Skip to content

Why did that step return that? Debugging a workflow you did not watch

A failed automation tells you it failed. The useful question is what the step actually received, what it sent back, and which model answered — and most tools cannot tell you.

Nishant Kumar 3 min read

An automation ran at 3am. This morning a customer got an email addressed to "Dear ," and you have no idea why.

This is the moment that decides whether an automation platform is usable. Not the building — building is the fun part and every tool demos it well. The moment that matters is the one where something ran, produced a wrong result, and you have to work out which of eleven steps went wrong and what it saw.

What most tools tell you

Usually: a red icon, a status of "failed", and a stack trace from whichever library threw. Sometimes a log line. Occasionally the final output.

What that gives you is the symptom. What you need is the input — because in nearly every case the step behaved perfectly correctly given what it was handed, and the actual bug is three steps upstream where a field came back empty.

"Dear ," is not an email bug. It is a lookup that returned nothing, and a template that did not notice.

The question is always the same

Whatever went wrong, the diagnosis follows one path:

  1. Which step produced the bad thing?
  2. What did that step receive?
  3. Did the {{ variable }} in it resolve to what you assumed?
  4. If a model was involved — which one, and what did it actually say?

Step 3 is where most of the time goes, because a template that renders empty looks identical to one that was never filled in. Dear {{ steps.lookup.output.name }}, and Dear , are the same line of configuration. Only the run knows the difference.

Keeping the receipt

So every block in Flowversal keeps one. After a run, the badge on the card is a button, and behind it is what that block actually did:

  • The output — what it returned, in full.
  • The input, as it resolved — not the template you wrote, the values it
  • became on this run. An empty variable is visible rather than inferred.

  • How long it took — and therefore which step is the slow one.
  • Which model answered, and what it cost — for any step that used one.

That last pair is worth dwelling on. A workflow with four AI steps can quietly cost twenty times what you expect because one of them is running on a frontier model that a default picked for you. Per-step cost turns that from a surprise at the end of the month into a number you can see on the card.

The cost is computed from the tokens the provider actually reported, not from a pre-flight estimate. An estimate has to guess how long the answer will be, and presenting a guess afterwards as what you were charged is a number that is wrong in a way nobody can check. Where a step reported no usage, it says so rather than showing zero.

Catching it before it runs

The better version of debugging is not debugging. A lot of failures are knowable before anything executes: a credential that was never connected, a required field left blank, a {{ steps.x }} pointing at a block that was renamed last week.

None of those need a run to detect. So pressing Run checks first, and if something would fail it says what, in a list where each row opens the exact card that fixes it. It is a small thing that removes most first-run failures — which are the ones most likely to make someone give up on a tool.

The general point

Automation software is judged on the day it goes wrong, not the day you build it. Anything that runs unattended will eventually do something you did not expect, and the difference between a platform you keep and one you abandon is whether that morning takes five minutes or the rest of the day.

Keep the receipt. You will need it.

  • debugging
  • observability