DEV Community

esteban-gore
esteban-gore

Posted on

Your automation's green checkmark is lying to you

Every no-code automation platform shows you the same comforting thing: a green checkmark. The run succeeded. Nothing to see here.

Except a green run only means one thing: the platform thinks it executed. It does not mean the invoice actually landed in QuickBooks, the Slack message actually posted, or all 200 rows actually got processed. Those are two very different claims, and the gap between them is where the expensive failures live — the ones that run green for four days while quietly doing nothing, until someone notices the deals never synced.

If you run automations in n8n, Zapier, or Make — especially for clients — you've probably met at least one of these.

The 5 silent failures

  1. Ran nothing. The workflow finished green and produced an empty result. No error, no output, no clue.
  2. Silent null accepted. An upstream field got renamed or emptied; your step swallowed the null and happily carried on. (The classic "11 deals evaporated because a lead-form field changed and Zapier accepted the null for four days.")
  3. Partial batch. 12 of 200 items threw inside a loop set to "continue on fail." The run finishes green. 188 succeeded; nobody counts the 12.
  4. Wrong content. A step returns HTTP 200 — with a body that's actually an HTML error page wearing an image/png content-type. Green check, garbage payload.
  5. Succeeded but wrong. The hardest one: the output is plausible but incorrect — the right shape, wrong record; the reply routed to the wrong thread; technically ran, missed the point. No status code will ever catch this.

The common thread: you're checking execution state, not the business outcome. Error triggers, retries, and HTTP status codes all answer "did it run?" None of them answer "did it do the thing?"

The fix: verify the outcome, from outside

The only reliable way to catch a "succeeded-while-doing-nothing" run is to check the result from outside the workflow — the same way you'd check a colleague's work instead of just asking whether they clicked save.

That splits into three cheap layers:

  • Deterministic guards (free, instant, no API key) for the mechanical modes: is the output empty? are required fields present and non-null? does the item count match what you expected? is the payload actually the type it claims?
  • An LLM outcome-judge for the semantic mode — "does this output actually achieve the stated intent?" This is the one thing a status code can't do, and the one thing a language model is genuinely good at.
  • A dead-man's-switch so the "never even ran" case pages you, instead of failing silent.

I got burned by enough of these that I packaged it into a tiny, zero-dependency drop-in. Here's the whole idea in one call:

const { verifyOutcome } = require('outcome-guard');

const result = await verifyOutcome({
  intent: "Create a QuickBooks invoice for the new Stripe customer",
  output: $json,                                   // your last real step's output
  spec: { requiredFields: ["invoice.id", "invoice.total"], expectedCount: null },
  llm: { provider: "anthropic", apiKey: $env.ANTHROPIC_API_KEY }  // optional
});

if (result.status !== "pass") {
  throw new Error("Outcome Guard: " + result.reasons.join(" | "));
}
Enter fullscreen mode Exit fullscreen mode

Drop it in an n8n Code node, a Make custom module, or a plain webhook after the real work of your flow. If the outcome doesn't hold up, it fails loudly — which is the whole point.

It's MIT, free, bring-your-own-key for the LLM part: github.com/esteban-gore/outcome-guard

I'm genuinely curious whether these five modes map to what bites you in production — and what I'm still missing. If there's interest, I'm exploring a hosted version (dashboards, alerting, no key wrangling); drop a note on the repo if that's something you'd use.

Top comments (0)