On this page
- The short answer: instrument the success, not the failure
- The five ways an automation fails
- Why silence is the default state
- The Silence Budget, the instrument that catches what errors miss
- What a silent failure costs while you do not know
- Classify the error before you retry it
- The error policy file
- Alerting people will not mute
- Instrument an existing workflow this afternoon
- The thirty minute audit
The short answer: instrument the success, not the failure
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 mode | What the run history shows | How it usually starts | What actually catches it |
|---|---|---|---|
| Crash | A run marked failed, with a stack trace | An exception, a 500, an expired credential | Any error alert. This is the easy one and the only one most teams handle |
| Stall | Nothing at all, because no run exists | A disabled trigger, a revoked token, a paused schedule, an exhausted quota | A heartbeat with a silence budget, since there is no run to attach an alert to |
| Silent wrong | A green tick | An empty array read as success, a filter matching nothing, a null coalesced into a default | A minimum item count plus an outcome assertion after the write |
| Partial | A green tick on a run that did half the work | A batch that died at item 300 of 500 with no transaction around it | Per-item accounting: attempted, succeeded, quarantined, and all three reconciled |
| Duplicate | Two green ticks and one angry customer | A retry issued after a write that had already succeeded | An idempotency key with a unique constraint, covered in idempotency in automation |
- 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.
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
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.
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?
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.
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.
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.
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.
Fill in your own volumes. The cost per bad record is your own estimate of remediation plus whatever the mistake does downstream.
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.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 matterTwo 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.
| Event | Route | Response expected | Why not louder or quieter |
|---|---|---|---|
| Silence budget breached | Page | Open the runbook now | No run exists, so nothing else will ever tell you. This is the alert that earns the pager |
| Outcome assertion failed | Page | Stop the workflow, check the record | The system believes it succeeded, so every minute of delay adds wrong data |
| Retries exhausted on one item | Ticket | Look today, not tonight | One stuck item rarely justifies a night, and the dead letter queue is already holding it safely |
| Dead letter depth over threshold | Ticket | Look today | Depth, not arrival, is the signal. A queue that grows is a fault; a queue that gets drained is a process |
| A single transient retry | Dashboard only | None | Paging on these is how a channel gets muted, and a muted channel takes the two page-worthy rows above down with it |
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.
- Write the two numbers
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.
- Add the heartbeat table
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.
- Put the watcher outside the platform
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.
- Classify every catch block
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.
- Add one outcome assertion
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.
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.
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?
How many times should an automation retry a failed step?
What belongs in a dead letter queue?
Should I alert on every error?
How do I detect that a trigger stopped firing?
Does error handling look different for AI steps in a workflow?
ChatGPTalker. "Error Handling That Stops Silent Automation Failures." chatgptalker.com, 2026-08-26. https://chatgptalker.com/guides/automation-error-handling/