$npx -y skills add czlonkowski/n8n-skills --skill n8n-error-handlingWire n8n error handling so failures are loud, structured, and recoverable. Use when building any webhook/API workflow, a scheduled or unattended workflow, or any path where a silent failure would drop user-visible work — and whenever the user mentions error handling, onError, con
| 1 | # n8n Error Handling |
| 2 | |
| 3 | By default, when an n8n node throws, the **whole workflow halts**. For an interactive run you're watching, that's fine — you see the red node and fix it. For anything unattended (a webhook API, a cron job, a queue worker, an agent tool), it's the wrong default: the caller gets a timeout or an empty 500, the operator gets no alert, and the symptom is "the integration just stopped working" with no log and no clue. |
| 4 | |
| 5 | This skill is about making failures **loud, structured, and recoverable** — and, best case, **self-healing** so transient blips never reach a human at all. |
| 6 | |
| 7 | The two ideas that prevent most silent failures: |
| 8 | |
| 9 | - **Per-node error outputs** — a node's failure routes down a second output you control, instead of killing the run. |
| 10 | - **A workflow-level error workflow** — a catch-all that fires for anything that escapes per-node handling (timeouts, crashes between nodes, unwired failures). |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## When you actually need this |
| 15 | |
| 16 | | Workflow shape | Error handling posture | |
| 17 | |---|---| |
| 18 | | Webhook / API (anything with `Respond to Webhook`) | **Required.** Every fallible node's error output wired; status code matches cause. | |
| 19 | | Scheduled / cron / queue worker / agent tool (unattended) | **Required.** A workflow-level error workflow, plus `retryOnFail` on network nodes. | |
| 20 | | Internal one-off you run and watch yourself | **Optional.** Default `onError: "stopWorkflow"` is fine — you'll see the red node and re-run. | |
| 21 | |
| 22 | The dividing line: **if anyone other than you sees the output** — a downstream system, an end user, an on-call engineer — the failure has to be handled, not swallowed. If you're the only watcher and the cost of failure is "I notice and re-run", looser is fine. |
| 23 | |
| 24 | --- |
| 25 | |
| 26 | ## The #1 silent trap: per-node error output is a TWO-step setup |
| 27 | |
| 28 | This is the single most common way an n8n workflow "handles" errors while actually swallowing them. Routing a node's failure to a handler takes **two** changes, and doing only one looks complete but misbehaves: |
| 29 | |
| 30 | 1. **Set `onError: "continueErrorOutput"`** on the node. This is what *creates* the second output. Without it, `main[1]` doesn't exist no matter what you wire. |
| 31 | 2. **Wire that error output** (`connections.<node>.main[1]`, i.e. `sourceIndex: 1`) to a real handler. Without a target, the error data is emitted into the void. |
| 32 | |
| 33 | Get one without the other and you hit a failure mode: |
| 34 | |
| 35 | | What you did | What happens at runtime | |
| 36 | |---|---| |
| 37 | | `onError` set, error output **not** wired | Error data is silently discarded. Downstream doesn't fire. The dashboard shows the run as **succeeded**. Worst case — no error logged anywhere. | |
| 38 | | Error output wired, `onError` **not** set | The slot never fires; the handler is unreachable. On failure the workflow just **halts** (default `stopWorkflow`). | |
| 39 | | Both done | Failure routes down `main[1]` to your handler. ✅ | |
| 40 | |
| 41 | ### Doing both with `n8n_update_partial_workflow` |
| 42 | |
| 43 | ```javascript |
| 44 | // 1) Turn on the error output (creates main[1]) |
| 45 | { type: "updateNode", nodeName: "HTTP Request", |
| 46 | changes: { onError: "continueErrorOutput" } } |
| 47 | |
| 48 | // 2) Wire the error output to a handler. sourceIndex: 1 = the error output. |
| 49 | { type: "addConnection", |
| 50 | source: "HTTP Request", |
| 51 | target: "Handle Error", |
| 52 | sourceIndex: 1 } |
| 53 | ``` |
| 54 | |
| 55 | `sourceIndex: 0` is the success path, `sourceIndex: 1` is the error path. (For IF nodes the aliases `branch: "true"`/`"false"` map to index 0/1; for a generic fallible node, use the explicit `sourceIndex: 1`.) |
| 56 | |
| 57 | **Then verify.** This trap doesn't surface in `validate_workflow` — a half-wired error output validates clean. Pull the workflow with `n8n_get_workflow` and confirm **both** halves: |
| 58 | |
| 59 | - The node's `onError` is `"continueErrorOutput"`. |
| 60 | - `connections["HTTP Request"].main[1]` contains your handler. |
| 61 | |
| 62 | Valid `onError` values: |
| 63 | |
| 64 | | Value | Effect | |
| 65 | |---|---| |
| 66 | | `"stopWorkflow"` (default) | Error halts the whole workflow. | |
| 67 | | `"continueRegularOutput"` | Error item flows out the **normal** output. Rare, usually wrong — downstream gets error-shaped data and keeps going. | |
| 68 | | `"continueErrorOutput"` | Error item flows out the **separate** error output (`main[1]`). The one you wire. | |
| 69 | |
| 70 | Full failure-mode catalog, fan-in/fan-out shapes, and verification: **NODE_ERROR_OUTPUTS.md**. |
| 71 | |
| 72 | --- |
| 73 | |
| 74 | ## Self-healing first: `retryOnFail` before you wire error paths |
| 75 | |
| 76 | Before you build error branches, absorb the transient failures so they never reach those branches. On **any node |