Workflow automation

Error handling that stops silent failures

The automation that crashes is not your problem. The one that runs green every morning while quietly writing nothing is, and it will do that for weeks unless you build the instrument that catches it.

On this page
  1. The short answer: instrument the success, not the failure
  2. The five ways an automation fails
  3. Why silence is the default state
  4. The Silence Budget, the instrument that catches what errors miss
  5. What a silent failure costs while you do not know
  6. Classify the error before you retry it
  7. The error policy file
  8. Alerting people will not mute
  9. Instrument an existing workflow this afternoon
  10. The thirty minute audit

The short answer: instrument the success, not the failure

The short answer

Alerting on errors catches only the failures that announce themselves, which are the cheap ones. Build the opposite instrument: every workflow declares how long it may go quiet and how few items a healthy run may process, and a human is paged when either is breached. Then classify each error as transient, terminal, poison or silent, retry only the transient ones, send terminal ones to a dead letter queue a named person reads on a schedule, and verify the outcome with a query rather than trusting a step that returned success.

  • SilenceThe most expensive failure produces no error at all, so an error-only alert policy is blind to it by construction.
  • Zero itemsA run that processed nothing is a failure wearing a success badge. Set a minimum item count per run and fail below it.
  • Four classesTransient, terminal, poison and silent. Each wants a different action, and treating them alike is why teams retry things that can never succeed.
  • VerbatimLog the provider's own error text. A summarised error is a debugging session you will have to run twice.

The pattern is consistent across every stack. Teams build the happy path, add a try and catch around the risky step, wire the catch to a Slack channel, and consider error handling finished. That design catches exceptions. It cannot catch the trigger that stopped firing three weeks ago, the filter that now matches nothing, or the write that returned 200 and changed no rows.

The five ways an automation fails

Only one of these five announces itself. Sorting a failure into the right row is the whole diagnostic, because each row is caught by a different instrument and none of the instruments overlap.

Failure modeWhat the run history showsHow it usually startsWhat actually catches it
CrashA run marked failed, with a stack traceAn exception, a 500, an expired credentialAny error alert. This is the easy one and the only one most teams handle
StallNothing at all, because no run existsA disabled trigger, a revoked token, a paused schedule, an exhausted quotaA heartbeat with a silence budget, since there is no run to attach an alert to
Silent wrongA green tickAn empty array read as success, a filter matching nothing, a null coalesced into a defaultA minimum item count plus an outcome assertion after the write
PartialA green tick on a run that did half the workA batch that died at item 300 of 500 with no transaction around itPer-item accounting: attempted, succeeded, quarantined, and all three reconciled
DuplicateTwo green ticks and one angry customerA retry issued after a write that had already succeededAn idempotency key with a unique constraint, covered in idempotency in automation
Sort the failure first. The instrument follows from the row.
Terms worth being precise about
Silent failure
A workflow run that reports success while producing a wrong or absent outcome. It is the most expensive failure class because detection depends on someone downstream noticing, which typically takes weeks.
Silence budget
The maximum time a workflow may go without a successful run before a human is paged. It is declared per workflow in advance, and it is the only instrument that detects a trigger that stopped firing.
Dead letter queue
A durable store holding every item the workflow could not process, together with the original payload and the verbatim error. A dead letter queue nobody reads on a schedule is a landfill, not a queue.
Poison item
An input that fails every time it is processed, usually because its shape is wrong. Poison items must be quarantined individually so one bad record cannot block the queue behind it.
Outcome assertion
A read issued after a write to confirm the world actually changed. It is the only check that distinguishes a successful API response from a successful business result.

Why silence is the default state

Silence is not an oversight. It is what every layer of the stack is built to produce, and four defaults conspire to make it happen.

  • Empty is not an error. An API that finds no records returns 200 with an empty array. The filter node treats an empty set as a valid set, the loop iterates zero times, and every step reports success.
  • Triggers fail outside the run. A schedule that stopped firing and an OAuth token that expired both produce no execution, so there is no failed run to alert on and nothing turns red.
  • Catch blocks swallow. Somebody wrapped the flaky step to stop the pages, the wrapper logs a warning and continues, and six months later that warning is the only record of a step that has not worked since spring.
  • Alert fatigue mutes. A channel that fires on every transient timeout gets muted within a fortnight, and it takes the real alerts down with it. Noise does not degrade an alerting system gradually, it disables it.
The green tick is a claim about the API, not the business

A 200 response means the request was accepted. It does not mean a row changed, the change was the one you intended, or the record is visible to the next system that reads it. If the outcome matters, read it back. One extra query per run is a price worth paying to convert a silent failure into a loud one.

The Silence Budget, the instrument that catches what errors miss

Framework

The Silence Budget

Every workflow you run gets five declarations, written once and stored beside the workflow. Together they invert the usual design: instead of waiting for something to go wrong loudly, you assert what right looks like and page a human when reality stops matching. It takes about twenty minutes per workflow and it is the highest return work in this entire guide.

01
Declare how long it may go quiet

Write the number of hours this workflow may run without a single successful execution before someone is paged. Hourly syncs get two. A weekly report gets eight days. The number forces a conversation nobody has otherwise: how long could this be broken before it hurts?

02
Heartbeat on success, not on failure

Every successful run writes a row with a timestamp, an item count and a duration. A separate watcher, running outside the workflow and outside the same platform, alerts when the newest row is older than the silence budget. This catches the stall, and nothing else does.

03
Set a volume floor and a volume ceiling

Declare the fewest and the most items a healthy run should process. Below the floor, something upstream broke. Above the ceiling, something is duplicating or a backfill is loose. Both conditions fail the run rather than logging a note.

04
Assert the outcome, not the status code

After the write, read the record back and compare one field you expect to have changed. If the read disagrees with the write, fail the run loudly. This single check converts most silent failures into crashes, and crashes you already handle.

05
Give every alert an owner and one runbook line

An alert with no named owner and no first action is noise with a notification attached. If you cannot write the sentence the responder should act on, delete the alert. A smaller alert set that people trust beats a complete one that everybody mutes.

The watcher living outside the platform matters more than it sounds. A workflow tool that is down cannot tell you it is down, and a monitor built inside the same tool shares its failure modes. Put the heartbeat check somewhere with an independent failure domain, even if that is a cron job on a small box you own.

What a silent failure costs while you do not know

The cost of a silent failure is a rate multiplied by a delay, and the delay is the only term you control cheaply. Detection lag is set by your instrumentation, not by the bug, which is why moving lag from days to hours is usually the highest value change available.

Cost of detection lag

Fill in your own volumes. The cost per bad record is your own estimate of remediation plus whatever the mistake does downstream.

0Records affected before you notice today
0Records affected with the budget in place
0Cost avoided per incident
Remediation is rarely the whole bill

Records written wrong tend to be read by something else before you find them. An invoice that went out, an email that fired, a lead scored and routed to the wrong owner: those consequences do not reverse when you fix the row. Put a number on the downstream effect for your worst workflow and the case for instrumentation stops needing an argument.

Classify the error before you retry it

A retry is only correct for one of the four error classes. Retrying the others wastes quota, hides the real fault and, on any non-idempotent write, actively creates damage.

Transient: retry with backoff and jitter

Timeouts, connection resets, 429s and most 5xx responses. The same request may well succeed later, so retry with exponential backoff and full jitter, cap the number of attempts, and alert only when the attempts are exhausted. The detail that decides whether this helps or hurts is in rate limits, retries and backoff.

Terminal: never retry

A 400, a 401, a 403, a 404 or a 422. The request is wrong, or you are not allowed to make it, and repetition changes nothing except your quota consumption. Send it straight to the dead letter queue with the payload attached. One exception deserves care: a 409 conflict frequently means your write already landed, so treat it as a signal to verify rather than to retry.

Poison: quarantine the item, keep the run alive

One record has a null where the schema promised a string, or a date in a format the parser rejects. If a poison item kills the whole batch, one bad record blocks every good record behind it, and you get a queue that stops moving on a Friday night. Quarantine the item, record the reason, process the rest, and review quarantine in a daily digest.

Silent: fail the run on purpose

Nothing threw. The item count was zero, or the write reported success and the read-back disagreed. This class only exists if you build the assertions that create it, which is exactly the point. You are manufacturing an error where the stack was content to give you a green tick.

The error policy file

Error handling scattered across catch blocks and node settings cannot be reviewed, because nobody can see all of it at once. Put the whole policy in one file per workflow, keep it in version control, and make the workflow read it at run time. Then a change to retry behaviour is a diff somebody approves rather than a checkbox somebody flips.

error-policy.yamlyaml
# error-policy.yaml
# One file per workflow. Lives in the repo next to the workflow it governs.

silence_budget_hours: 2       # longest this workflow may go quiet before a human is paged
expected_min_items_per_run: 1 # a run that processed zero items is a failure, not a success

classes:

  transient:                  # retry, then stop trying, then tell someone once
    match:
      http_status: [408, 425, 429, 500, 502, 503, 504]
      exception: [ConnectionReset, ReadTimeout, TemporarilyUnavailable]
    action: retry
    max_attempts: 5
    backoff: { base_ms: 500, factor: 2, cap_ms: 60000, jitter: full }
    alert: on_exhaustion_only

  terminal:                   # never retry, a retry cannot change the answer
    match:
      http_status: [400, 401, 403, 404, 409, 422]
    action: dead_letter
    alert: immediate

  poison:                     # the item is bad, not the run
    match:
      exception: [ValidationError, SchemaMismatch, DecodeError]
    action: quarantine_item
    continue_run: true
    alert: daily_digest

  silent:                     # the run reported success and the outcome is still wrong
    detect:
      - items_processed == 0 and expected_min_items_per_run > 0
      - records_written != items_processed
      - post_write_verification_failed
    action: fail_the_run
    alert: immediate

envelope:                     # every error writes all of these, no exceptions
  run_id: uuid
  workflow: string
  step: string
  idempotency_key: string
  class: transient | terminal | poison | silent
  attempt: int
  error_verbatim: string      # the provider's own text, never your summary of it
  request_fingerprint: sha256(method + url + sorted_body_keys)
  payload_ref: object_store_pointer
  occurred_at: rfc3339

routing:
  page:      [terminal, silent, silence_budget_breached]
  ticket:    [transient_exhausted, dead_letter_depth_over_10]
  digest:    [poison]
  dashboard: [transient_attempt_1_to_4]
  never:     [individual_retry_attempts]   # muted alerts kill the ones that matter

Two fields in there do the heavy lifting. The request fingerprint lets you group thousands of failures into the handful of distinct faults they actually represent, which turns an unreadable error channel into a short list. The verbatim error field stops the habit of catching an exception and logging your own paraphrase of it, which destroys the one piece of evidence the provider gave you.

Alerting people will not mute

Route by consequence, not by severity label. The question is never how bad the event sounds. It is what a human should do about it within the next hour, and whether that is worth waking someone for.

EventRouteResponse expectedWhy not louder or quieter
Silence budget breachedPageOpen the runbook nowNo run exists, so nothing else will ever tell you. This is the alert that earns the pager
Outcome assertion failedPageStop the workflow, check the recordThe system believes it succeeded, so every minute of delay adds wrong data
Retries exhausted on one itemTicketLook today, not tonightOne stuck item rarely justifies a night, and the dead letter queue is already holding it safely
Dead letter depth over thresholdTicketLook todayDepth, not arrival, is the signal. A queue that grows is a fault; a queue that gets drained is a process
A single transient retryDashboard onlyNonePaging on these is how a channel gets muted, and a muted channel takes the two page-worthy rows above down with it
Four routes and one deliberate silence. Anything that does not fit a row does not get an alert.

One more rule worth enforcing: every alert names the workflow, the run id and the first action, in the message itself. A responder who has to open three tools to find out what broke is a responder who will start ignoring the alert. If your automation platform cannot produce that message, put the alerting outside the platform, the same way you did with the heartbeat. The teams we build workflow automation for usually keep both in their existing monitoring stack rather than the automation tool.

Instrument an existing workflow this afternoon

You do not need a rebuild. Take your most important workflow and do these five things in order, because each one makes the next easier to reason about.

  1. Write the two numbers10 minutes

    The silence budget in hours and the minimum items a healthy run processes. Ask the person who depends on the output, not the person who built the workflow, because only the first one knows how long broken is tolerable.

  2. Add the heartbeat table20 minutes

    One table with run id, workflow name, finished timestamp, item count and duration. Every successful run writes a row. This single table becomes the source for the stall alert, the volume floor and every question about whether it ran.

  3. Put the watcher outside the platform20 minutes

    A scheduled job somewhere else queries that table and alerts when the newest row is older than the budget, or when the item count sits under the floor. Keep it dull. The watcher should have no dependencies that can break with the thing it watches.

  4. Classify every catch block45 minutes

    Walk each error path and label it transient, terminal, poison or silent. Retries stay only on transient. Terminal paths go to the dead letter store with the payload. Poison paths quarantine the item and let the run continue.

  5. Add one outcome assertion20 minutes

    Pick the most consequential write in the workflow, read the record back, and compare one field. If it disagrees, throw. Start with a single assertion on the write that would hurt most, then add more only where the pain justifies it.

The order matters. Classifying error paths before the heartbeat exists gives you tidier failures and still no way to detect a stall, and the stall is the failure that runs longest before anyone notices.

The thirty minute audit

Run this against any workflow already in production. Every unticked box is a failure mode currently invisible to you.

Silent failure audit
0 of 10 done

Most teams tick three or four. The two that pay back fastest are the heartbeat and the zero-item check, because between them they cover the stall and the silent wrong, which are the two modes that run longest before anyone notices. If the workflow lives on a visual canvas, choosing between n8n, Make and custom code covers which of these checks the canvas can hold and which need to sit outside it.

Cite this

ChatGPTalker, Error Handling That Stops Silent Failures: alert on the absence of success rather than the presence of errors, using a declared silence budget, a volume floor and an outcome assertion per workflow.

Questions readers ask next

What is a silent failure in workflow automation?
A run that reports success while producing a wrong or missing outcome. Common causes are an empty API response treated as a valid result, a filter that stopped matching, or a write that returned 200 without changing a row. It is the most expensive failure class because nothing turns red and detection depends on somebody downstream noticing.
How many times should an automation retry a failed step?
Between three and five attempts for transient errors, with exponential backoff and full jitter, and a hard cap on total elapsed time. Terminal errors like 400 or 422 should never be retried at all. What matters more than the count is that the operation carries an idempotency key, so a retry after a successful write cannot duplicate it.
What belongs in a dead letter queue?
The original payload, the verbatim provider error, the workflow and step names, the run id, the idempotency key, the attempt count and a timestamp. Without the original payload you cannot replay the item, which makes the queue an archive of regrets. Give it a named reviewer and a scheduled review, and alert on queue depth rather than on each arrival.
Should I alert on every error?
No, and doing so is how alerting stops working. Page only on events that need action within the hour: a breached silence budget and a failed outcome assertion. Route exhausted retries and growing dead letter depth to a ticket for the same working day. Leave individual transient retries on a dashboard where they inform without interrupting anyone.
How do I detect that a trigger stopped firing?
You cannot detect it from inside the workflow, because no run exists to raise an error. The only instrument that works is a heartbeat: successful runs write a timestamped row, and a watcher living outside the automation platform alerts when the newest row is older than the declared silence budget for that workflow.
Does error handling look different for AI steps in a workflow?
The classification is the same but two failure modes get more likely. Model output can be well formed and wrong, so validate it against a schema and treat a schema miss as poison rather than transient. Providers also change behaviour without a release note, so keep the verbatim error and pin the prompt version in the trace.
Cite this

ChatGPTalker. "Error Handling That Stops Silent Automation Failures." chatgptalker.com, 2026-08-26. https://chatgptalker.com/guides/automation-error-handling/

Rather have it built than read about it?

Send the process you want automated. You get a scoped plan back, with the build shape, the stack and a realistic timeline.

Start a project