n8n-error-handling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesen8n Error Handling
n8n 错误处理
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.
This skill is about making failures loud, structured, and recoverable — and, best case, self-healing so transient blips never reach a human at all.
The two ideas that prevent most silent failures:
- Per-node error outputs — a node's failure routes down a second output you control, instead of killing the run.
- A workflow-level error workflow — a catch-all that fires for anything that escapes per-node handling (timeouts, crashes between nodes, unwired failures).
默认情况下,当n8n节点抛出异常时,整个工作流会停止运行。对于你正在监控的交互式运行来说,这没问题——你会看到红色节点并修复问题。但对于无人值守的场景(如Webhook API、定时任务、队列 worker、Agent工具),这一默认行为并不合适:调用方会收到超时或空的500响应,运维人员不会收到告警,最终表现为“集成突然停止工作”,却没有日志也没有排查线索。
本方案旨在让故障清晰可见、结构化且可恢复——最佳情况下实现自愈,让临时故障无需人工介入即可自行解决。
避免静默故障的两个核心思路:
- 节点级错误输出——节点故障时会路由到你可控的第二个输出,而非终止整个运行。
- 工作流级错误处理工作流——一个兜底机制,用于捕获所有节点级处理未覆盖的故障(如超时、节点间崩溃、未配置错误输出的故障)。
When you actually need this
何时需要启用该机制
| Workflow shape | Error handling posture |
|---|---|
Webhook / API (anything with | Required. Every fallible node's error output wired; status code matches cause. |
| Scheduled / cron / queue worker / agent tool (unattended) | Required. A workflow-level error workflow, plus |
| Internal one-off you run and watch yourself | Optional. Default |
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.
| 工作流类型 | 错误处理策略 |
|---|---|
Webhook / API(包含 | 必须配置。每个可能出错的节点的错误输出都需配置连接,状态码需与故障原因匹配。 |
| 定时/计划任务 / 队列 worker / Agent工具(无人值守) | 必须配置。需设置工作流级错误处理工作流,同时在网络节点上启用 |
| 你自行运行并监控的内部一次性工作流 | 可选。默认的 |
判断标准:如果除你之外的任何人会看到输出(如下游系统、终端用户、值班工程师),则必须处理故障,而非忽略。如果只有你监控,且故障代价仅为“我发现后重新运行”,则可以放宽要求。
The #1 silent trap: per-node error output is a TWO-step setup
最常见的静默陷阱:节点级错误输出需两步配置
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:
- Set on the node. This is what creates the second output. Without it,
onError: "continueErrorOutput"doesn't exist no matter what you wire.main[1] - Wire that error output (, i.e.
connections.<node>.main[1]) to a real handler. Without a target, the error data is emitted into the void.sourceIndex: 1
Get one without the other and you hit a failure mode:
| What you did | What happens at runtime |
|---|---|
| Error data is silently discarded. Downstream doesn't fire. The dashboard shows the run as succeeded. Worst case — no error logged anywhere. |
Error output wired, | The slot never fires; the handler is unreachable. On failure the workflow just halts (default |
| Both done | Failure routes down |
这是n8n工作流“看似处理错误但实际静默吞掉错误”的最常见原因。将节点故障路由到处理程序需要两步操作,仅完成其中一步看似配置完成,但实际行为异常:
- 在节点上设置——这会创建第二个输出。如果不设置,无论你如何连接,
onError: "continueErrorOutput"都不会存在。main[1] - 连接该错误输出(,即
connections.<node>.main[1])到实际的处理程序。如果没有目标节点,错误数据会被丢弃。sourceIndex: 1
仅完成其中一步会导致以下故障模式:
| 操作内容 | 运行时表现 |
|---|---|
设置了 | 错误数据被静默丢弃。下游节点不会触发。控制台显示运行成功。最糟情况——无任何错误日志。 |
连接了错误输出但未设置 | 该输出槽永远不会触发;处理程序无法访问。故障发生时工作流直接停止(默认 |
| 两步均完成 | 故障会通过 |
Doing both with n8n_update_partial_workflow
n8n_update_partial_workflow使用n8n_update_partial_workflow
完成两步配置
n8n_update_partial_workflowjavascript
// 1) Turn on the error output (creates main[1])
{ type: "updateNode", nodeName: "HTTP Request",
changes: { onError: "continueErrorOutput" } }
// 2) Wire the error output to a handler. sourceIndex: 1 = the error output.
{ type: "addConnection",
source: "HTTP Request",
target: "Handle Error",
sourceIndex: 1 }sourceIndex: 0sourceIndex: 1branch: "true""false"sourceIndex: 1Then verify. This trap doesn't surface in — a half-wired error output validates clean. Pull the workflow with and confirm both halves:
validate_workflown8n_get_workflow- The node's is
onError."continueErrorOutput" - contains your handler.
connections["HTTP Request"].main[1]
Valid values:
onError| Value | Effect |
|---|---|
| Error halts the whole workflow. |
| Error item flows out the normal output. Rare, usually wrong — downstream gets error-shaped data and keeps going. |
| Error item flows out the separate error output ( |
Full failure-mode catalog, fan-in/fan-out shapes, and verification: NODE_ERROR_OUTPUTS.md.
javascript
// 1) 启用错误输出(创建main[1])
{ type: "updateNode", nodeName: "HTTP Request",
changes: { onError: "continueErrorOutput" } }
// 2) 将错误输出连接到处理程序。sourceIndex: 1 = 错误输出。
{ type: "addConnection",
source: "HTTP Request",
target: "Handle Error",
sourceIndex: 1 }sourceIndex: 0sourceIndex: 1branch: "true""false"sourceIndex: 1务必验证。这种陷阱不会在中暴露——半配置的错误输出会验证通过。使用拉取工作流并确认两步均已完成:
validate_workflown8n_get_workflow- 节点的设置为
onError。"continueErrorOutput" - 包含你的处理程序。
connections["HTTP Request"].main[1]
onError| 值 | 效果 |
|---|---|
| 错误会终止整个工作流。 |
| 错误项会从正常输出流出。很少使用,通常不合适——下游会收到错误格式的数据并继续运行。 |
| 错误项会从独立的错误输出( |
完整的故障模式目录、扇入/扇出结构及验证方法:详见NODE_ERROR_OUTPUTS.md。
Self-healing first: retryOnFail
before you wire error paths
retryOnFail优先实现自愈:配置错误分支前先启用retryOnFail
retryOnFailBefore you build error branches, absorb the transient failures so they never reach those branches. On any node that calls a network service — HTTP Request, comms (Gmail/Slack/Discord), databases, AI nodes, third-party integrations — set node-level retry:
javascript
{ type: "updateNode", nodeName: "HTTP Request",
changes: {
retryOnFail: true,
maxTries: 3,
waitBetweenTries: 5000 // ms
} }Why this comes first: a 429 or a brief upstream hiccup will retry and usually succeed on its own. The error output then fires only on real, persistent failures — so your 5xx responses and on-call alerts reflect actual problems instead of noise.
Engine limits to know: retry fires on any error (there's no per-status-code filter), caps at 5, and caps at 5000ms — so 5000 is both the max and a sensible default. See n8n-node-configuration (NODE_FAMILY_GOTCHAS.md) for node-specific notes.
maxTrieswaitBetweenTries在构建错误分支之前,先处理临时故障,使其不会触发错误分支。对于任何调用网络服务的节点——HTTP Request、通信节点(Gmail/Slack/Discord)、数据库、AI节点、第三方集成——设置节点级重试:
javascript
{ type: "updateNode", nodeName: "HTTP Request",
changes: {
retryOnFail: true,
maxTries: 3,
waitBetweenTries: 5000 // 毫秒
} }为什么要优先配置:429(请求过多)或上游短暂故障会自动重试,通常会成功。错误输出仅在真实的、持续性的故障时触发——这样你的5xx响应和值班告警反映的是实际问题,而非噪音。
需要了解的引擎限制:重试会在任何错误时触发(无按状态码过滤的选项),上限为5,上限为5000毫秒——因此5000既是最大值,也是合理的默认值。节点特定说明详见n8n-node-configuration(NODE_FAMILY_GOTCHAS.md)。
maxTrieswaitBetweenTriesAPI workflows: the canonical shape
API工作流:标准结构
A webhook-triggered workflow that responds to its caller has one rule that overrides everything else: no hanging branches. Every path — success and every error — must end at a , or the caller sits there until it times out.
Respond to WebhookWebhook (responseMode: "responseNode")
├── validate input → process → Respond (200, body)
└── (any fallible node's error output → sourceIndex 1)
→ Respond (4xx/5xx, structured error body)
→ optional: log full error privately / notifyThree things make this work:
- Fan-in to one error responder. Many fallible nodes can route their to a single
main[1]node. Keeps the graph readable.Respond - Validation failures (4xx) are checked upstream, not via error outputs. A missing field isn't a node crashing — it's an expected outcome with a known response. Branch on it with IF/Switch (or the schema validator below) and return 400/401/403/404 directly. Error outputs are for unexpected failures (5xx).
- defaults to 200 — even on error branches. This is its own silent trap (see RESPONSE_SHAPES.md and n8n-node-configuration NODE_FAMILY_GOTCHAS.md): an error branch that returns 200 with an error body looks like success to the caller's HTTP client, so their error handling never fires. Set
responseCodeexplicitly on every Respond node.responseCode
Webhook触发的工作流在响应调用方时有一个核心规则:不能有未终止的分支。每个路径——成功路径和所有错误路径——都必须以结束,否则调用方会一直等待直到超时。
Respond to WebhookWebhook (responseMode: "responseNode")
├── 验证输入 → 处理 → Respond (200, 响应体)
└──(任何可能出错的节点的错误输出 → sourceIndex 1)
→ Respond (4xx/5xx, 结构化错误体)
→ 可选:私下记录完整错误 / 发送通知实现该结构的三个关键点:
- 所有错误路径汇聚到一个错误响应节点。多个可能出错的节点可以将其路由到同一个
main[1]节点。保持流程图可读性。Respond - 验证失败(4xx)需在上游检查,而非通过错误输出。缺失字段不是节点崩溃——这是预期结果,有已知的响应。使用IF/Switch(或下方的模式验证器)进行分支,直接返回400/401/403/404。错误输出用于处理意外故障(5xx)。
- 默认值为200——即使在错误分支中。这是另一个静默陷阱(详见RESPONSE_SHAPES.md和n8n-node-configuration NODE_FAMILY_GOTCHAS.md):返回200状态码但包含错误体的错误分支,在调用方的HTTP客户端看来是成功的,因此他们的错误处理逻辑不会触发。需在每个Respond节点上显式设置
responseCode。responseCode
Input validation: the Set-node schema validator
输入验证:Set节点模式验证器
For any endpoint doing structured input validation, run the check as an IIFE inside a single Set node rather than a chain of IF/Switch nodes per field. One node validates the whole payload, returns , and an IF branches on → your logic (200) or a 400 Respond that echoes the schema back so the caller can self-correct. It's also dramatically faster than a recursive validator in a Code node + sub-workflow. The full pattern, the constraint cookbook, and the expression-escaping gotchas live in API_WORKFLOWS.md.
{ valid, validationError, details, requiredSchema }valid对于任何需要结构化输入验证的端点,可在单个Set节点内使用立即执行函数(IIFE)完成检查,而非为每个字段设置一系列IF/Switch节点。一个节点即可验证整个负载,返回,然后通过IF节点根据分支到业务逻辑(200响应)或返回400响应并回显模式,以便调用方自行修正。这比在Code节点+子工作流中使用递归验证器快得多。完整模式、约束指南及表达式转义陷阱详见API_WORKFLOWS.md。
{ valid, validationError, details, requiredSchema }validResponse shapes: map cause → status code
响应格式:根据故障原因映射状态码
A 5xx with is technically an error response and practically useless. And not every failure is a 5xx. Match the status code to why the request failed, because the caller branches on it: their monitoring alerts on 5xx (your fault) but not 4xx (their fault), and 5xx suggests "retry" while 4xx suggests "don't".
text/plain "Internal Server Error"The common mistake: wiring everything — including bad input — to one that returns 500 . Now the caller can't tell their bug from your outage, and your error rates can't separate real incidents from client noise.
Respondinternal_error| Cause | Status | | Where it's handled |
|---|---|---|---|
| Required field missing / wrong type | 400 | | Upstream check (schema validator / IF), not error output |
| Auth missing or invalid | 401 | | Upstream check |
| Authenticated but not allowed | 403 | | Upstream check |
| Resource ID valid in request, absent in your data | 404 | | Branch on the lookup result, not its error |
| Conflicts with current state (duplicate, race) | 409 | | Detect with logic |
| Caller exceeded rate limit | 429 | | Set |
| Node threw, cause unknown | 500 | | Error output path |
| Third-party API returned an error | 502 | | Error output of the HTTP node |
| Can't process right now (downstream down) | 503 | | Detect specific error, hint retry |
| Third-party API timed out | 504 | | Error output filtered by message |
So there are two distinct flows: 4xx is decided before the work (IF/Switch + dedicated Respond), 5xx comes out of error outputs ("we tried, it broke").
One Respond, expression-driven code. When error paths differ only by number and message (same body shape, same headers), don't fan out to N Respond nodes through a Switch. The Respond node accepts expressions in both and body — compute the code inline:
Response Codejavascript
// Response Code field on a single Respond to Webhook:
{{ (() => {
const msg = $json.error?.message || $json.message || '';
if (msg.includes('INVALID_ID')) return 400;
if (/429|too many/i.test(msg)) return 429;
if (/timeout/i.test(msg)) return 504;
if (/upstream|llm|api/i.test(msg)) return 502;
return 500;
})() }}Reserve Switch + multiple Responds for paths that diverge structurally (different headers, different body shapes, redirects). Same shape with a different number is one expression-driven Respond.
The default envelope is — the HTTP status already says success-vs-failure, so no flag. Never leak internals (stack traces, SQL, upstream bodies, tokens) into the response — log those privately, return a sanitized message. Correlation IDs, , validation , and the full do-not-leak list are in RESPONSE_SHAPES.md.
{ "error": "<code>", "message": "<human text>" }ok: falseretry_afterdetails返回5xx状态码+在技术上是错误响应,但实际上毫无用处。并非所有故障都是5xx。状态码需与请求失败的原因匹配,因为调用方会根据状态码进行分支处理:他们的监控会针对5xx告警(你的问题),但不会针对4xx(他们的问题);5xx提示“可重试”,而4xx提示“请勿重试”。
text/plain "Internal Server Error"常见错误:将所有故障——包括无效输入——都路由到一个返回500 的节点。这样调用方无法区分是他们的bug还是你的服务中断,你的错误率也无法区分真实事件与客户端噪音。
internal_errorRespond| 故障原因 | 状态码 | | 处理位置 |
|---|---|---|---|
| 必填字段缺失 / 类型错误 | 400 | | 上游检查(模式验证器 / IF),而非错误输出 |
| 认证信息缺失或无效 | 401 | | 上游检查 |
| 已认证但无权限 | 403 | | 上游检查 |
| 请求中的资源ID有效,但你的数据中不存在 | 404 | | 根据查询结果分支,而非查询错误 |
| 与当前状态冲突(重复、竞态) | 409 | | 通过逻辑检测 |
| 调用方超出速率限制 | 429 | | 设置 |
| 节点抛出异常,原因未知 | 500 | | 错误输出路径 |
| 第三方API返回错误 | 502 | | HTTP节点的错误输出 |
| 当前无法处理(下游服务宕机) | 503 | | 检测特定错误,提示可重试 |
| 第三方API超时 | 504 | | 根据错误消息过滤的错误输出 |
因此存在两种不同的流程:4xx在执行工作前确定(IF/Switch + 专用Respond节点),5xx来自错误输出(“我们尝试了,但失败了”)。
单个Respond节点,通过表达式驱动状态码。当错误路径仅在状态码和消息上不同(响应体结构相同、头信息相同)时,无需通过Switch节点分支到N个Respond节点。Respond节点的和响应体均支持表达式——可内联计算状态码:
Response Codejavascript
// Respond to Webhook节点的Response Code字段:
{{ (() => {
const msg = $json.error?.message || $json.message || '';
if (msg.includes('INVALID_ID')) return 400;
if (/429|too many/i.test(msg)) return 429;
if (/timeout/i.test(msg)) return 504;
if (/upstream|llm|api/i.test(msg)) return 502;
return 500;
})() }}仅当路径在结构上不同(不同的头信息、不同的响应体结构、重定向)时,才使用Switch + 多个Respond节点。结构相同仅状态码不同的情况,使用单个表达式驱动的Respond节点即可。
默认响应格式为——HTTP状态码已表明成功或失败,因此无需标志。绝对不要泄露内部信息(堆栈跟踪、SQL语句、上游响应体、令牌)到响应中——私下记录这些信息,返回经过脱敏的消息。关联ID、、验证及完整的禁止泄露列表详见RESPONSE_SHAPES.md。
{ "error": "<code>", "message": "<人类可读文本>" }ok: falseretry_afterdetailsWorkflow-level error workflow (the catch-all)
工作流级错误处理工作流(兜底机制)
Per-node outputs handle the failures you anticipated on the nodes you remembered to wire. An error workflow catches everything else: a node you forgot to wire, a crash between nodes, a whole-workflow timeout, a trigger failure. For unattended workflows this is the safety net that turns "it silently stopped" into "an alert arrived".
Build it as a separate workflow starting with an Error Trigger node. n8n invokes it with the failure context:
json
{
"execution": { "id": "...", "url": "...", "lastNodeExecuted": "Fetch order",
"error": { "name": "NodeApiError", "message": "...", "timestamp": 1715000000000 } },
"workflow": { "id": "...", "name": "Sync Stripe customers" }
}Minimal version — capture → notify:
Error Trigger → Set (build alert from execution + error) → Slack/email (post to #incidents)A good alert includes the workflow name, a link to the editor and a link to the failed execution, the failed node name, and the real error message (not "Workflow failed"). Field expressions and the optional "fetch the failing input via the n8n node" upgrade are in ERROR_WORKFLOWS.md.
Two traps worth flagging up front:
- The recursion trap. If the error workflow notifies Slack and Slack is what's down, the error workflow fails too — and the original error vanishes. Notify on a different channel than your monitored workflows use (most workflows alert Slack → error workflow uses email), and add a fallback (write to a Data Table) so a failed notification still leaves a trace.
- A "handled" error won't bubble up. If a node's error output is wired to a no-op that drops the data, n8n considers the error handled and the error workflow does not fire. Only catch per-node when you're actually doing something with the error.
What the community MCP can't do: assigning the error workflow (instance default or per-workflow override) is an n8n UI setting — Workflow Settings → Error Workflow. There is no MCP tool to set it. Build the error workflow with the MCP, then tell the user the exact UI step to wire it up, and to repeat it (or set the instance default) for every unattended workflow.
节点级输出处理你已预见的、记得配置的节点故障。错误处理工作流会捕获其他所有故障:你忘记配置的节点、节点间的崩溃、整个工作流超时、触发器故障。对于无人值守工作流来说,这是将“静默停止”转化为“收到告警”的安全网。
将其构建为一个独立的工作流,以Error Trigger节点开头。n8n会将故障上下文传入该工作流:
json
{
"execution": { "id": "...", "url": "...", "lastNodeExecuted": "Fetch order",
"error": { "name": "NodeApiError", "message": "...", "timestamp": 1715000000000 } },
"workflow": { "id": "...", "name": "Sync Stripe customers" }
}最简版本——捕获 → 通知:
Error Trigger → Set(根据执行信息和错误构建告警)→ Slack/邮件(发送到#incidents频道)一个良好的告警应包含工作流名称、编辑器链接、失败执行的链接、失败节点名称,以及真实的错误消息(而非“工作流失败”)。字段表达式及可选的“通过n8n节点获取失败输入”升级详见ERROR_WORKFLOWS.md。
需要提前注意的两个陷阱:
- 递归陷阱。如果错误处理工作流通过Slack发送通知,而Slack服务宕机,错误处理工作流也会失败——原始错误会消失。请使用与受监控工作流不同的通知渠道(大多数工作流通过Slack告警 → 错误处理工作流使用邮件),并添加兜底方案(写入Data Table),这样即使通知失败,仍会留下痕迹。
- “已处理”的错误不会冒泡。如果节点的错误输出连接到一个丢弃数据的空操作节点,n8n会认为错误已被处理,错误处理工作流不会触发。仅当你实际要处理错误时,才使用节点级捕获。
社区MCP无法完成的操作:分配错误处理工作流(实例默认或按工作流覆盖)是n8n的UI设置——工作流设置 → 错误处理工作流。没有MCP工具可以设置。使用MCP构建错误处理工作流后,告知用户具体的UI步骤来配置,并提醒他们为每个无人值守工作流重复配置(或设置实例默认值)。
What's NOT available via the community MCP
社区MCP无法实现的功能
| Want to do | Reality |
|---|---|
| Set a workflow's Error Workflow setting | UI only (Workflow Settings → Error Workflow). No MCP tool. Build the workflow, then hand the user the UI step. |
| Toggle other workflow settings (Save Execution Data, timezone, timeout, caller policy) | UI only. |
| Enable instance-wide error logging (Sentry, server logs) | Instance config, outside n8n workflows entirely. |
What the MCP can do: build the error workflow, set / on nodes (/), wire error outputs ( with ), validate (, ), auto-fix common issues (), test (), and inspect failures ().
onErrorretryOnFailupdateNodepatchNodeFieldaddConnectionsourceIndex: 1validate_workflown8n_validate_workflown8n_autofix_workflown8n_test_workflown8n_executions| 需求 | 实际情况 |
|---|---|
| 设置工作流的错误处理工作流配置 | 仅支持UI操作(工作流设置 → 错误处理工作流)。无MCP工具。构建工作流后,告知用户UI步骤。 |
| 切换其他工作流设置(保存执行数据、时区、超时、调用方策略) | 仅支持UI操作。 |
| 启用实例级错误日志(Sentry、服务器日志) | 属于实例配置,完全在n8n工作流之外。 |
MCP可以实现的功能:构建错误处理工作流、在节点上设置/(/)、连接错误输出(搭配)、验证(、)、自动修复常见问题()、测试()、检查故障()。
onErrorretryOnFailupdateNodepatchNodeFieldaddConnectionsourceIndex: 1validate_workflown8n_validate_workflown8n_autofix_workflown8n_test_workflown8n_executionsAnti-patterns
反模式
| Anti-pattern | What goes wrong | Fix |
|---|---|---|
| Error silently discarded; run shows as succeeded | Wire |
Error output wired but | Slot never fires; handler unreachable; workflow halts on failure | Set |
| Webhook → process → respond, no error branch | Caller gets a timeout or n8n's generic 500 | Wire every fallible node's error output to a Respond |
Error branch returns 200 with an | Caller's client reads success; their error handling never fires | Set |
One 500 | Caller can't tell their bad input from your outage | Map cause → status (4xx caller, 5xx you) |
| Catching errors in a Code node and returning them as data | Downstream processes error-shaped data and continues | Let it throw; use |
Network node with no | Every transient 429/blip surfaces as a 5xx; alerts fire on noise | |
| Switch → N Responds differing only by status code | 5 nodes for what's one Respond | Compute the code inline in one expression-driven Respond |
| Unattended workflow with no error workflow | A genuine failure goes nowhere | Build an Error Trigger workflow + assign it in the UI |
| Error workflow notifies the same channel the workflows monitor | Channel down → error workflow also fails → error vanishes | Use a different channel + a Data Table fallback |
Leaking | Exposes internals to callers/attackers | Log privately, return a sanitized message |
| 反模式 | 问题 | 修复方案 |
|---|---|---|
设置了 | 错误被静默丢弃;运行显示为成功 | 将 |
连接了错误输出但未设置 | 输出槽永远不会触发;处理程序无法访问;故障时工作流停止 | 设置 |
| Webhook → 处理 → 响应,但无错误分支 | 调用方收到超时或n8n通用的500响应 | 将每个可能出错的节点的错误输出连接到Respond节点 |
错误分支返回200状态码及 | 调用方客户端认为是成功;他们的错误处理逻辑不会触发 | 在错误分支的Respond节点上显式设置4xx/5xx的 |
所有故障都返回500 | 调用方无法区分是他们的无效输入还是你的服务中断 | 根据原因映射状态码(4xx为调用方问题,5xx为我方问题) |
| 在Code节点中捕获错误并作为数据返回 | 下游处理错误格式的数据并继续运行 | 让错误抛出;使用 |
网络节点未配置 | 每个临时的429/故障都会触发5xx响应;告警被噪音淹没 | 设置 |
| Switch → N个仅状态码不同的Respond节点 | 用5个节点实现单个节点即可完成的功能 | 在单个表达式驱动的Respond节点内联计算状态码 |
| 无人值守工作流未配置错误处理工作流 | 真实故障无任何通知 | 构建Error Trigger工作流 + 在UI中分配 |
| 错误处理工作流使用与受监控工作流相同的通知渠道 | 渠道宕机 → 错误处理工作流也失败 → 错误消失 | 使用不同的渠道 + Data Table兜底 |
将 | 向调用方/攻击者暴露内部信息 | 私下记录,返回脱敏消息 |
Reference files
参考文件
| File | Read when |
|---|---|
| NODE_ERROR_OUTPUTS.md | Wiring a per-node error output on individual fallible nodes |
| API_WORKFLOWS.md | Building/reviewing a webhook → Respond workflow, including the schema validator |
| RESPONSE_SHAPES.md | Defining response body conventions, status codes, and what not to leak |
| ERROR_WORKFLOWS.md | Setting up the workflow-level catch-all for unattended workflows |
| 文件 | 阅读场景 |
|---|---|
| NODE_ERROR_OUTPUTS.md | 为单个可能出错的节点配置节点级错误输出时 |
| API_WORKFLOWS.md | 构建/审查Webhook → Respond工作流,包括模式验证器时 |
| RESPONSE_SHAPES.md | 定义响应体规范、状态码及禁止泄露内容时 |
| ERROR_WORKFLOWS.md | 为无人值守工作流设置工作流级兜底机制时 |
Integration with other skills
与其他技能的集成
- n8n-workflow-patterns — the webhook/API and scheduled patterns are where error handling lives. Use it for the overall shape; use this skill to harden it.
- n8n-node-configuration — /
onErrorare node config; NODE_FAMILY_GOTCHAS.md covers the Webhook/Respond response-code traps in depth.retryOnFail - n8n-validation-expert — the half-wired error output (one of the two steps missing) is a connection/config audit item, not a validation error. This skill is the fix.
- n8n-expression-syntax — the expression-driven and the alert-message expressions rely on correct
Response Codesyntax and{{ }}access.$json.error - n8n-code-javascript / n8n-code-python — if you catch errors inside a Code node, decide deliberately: re-throw to use the error output, or handle and continue. Don't return error-shaped data and pretend it succeeded.
- n8n-code-tool — an agent's Code Tool surfaces thrown errors back to the LLM, which then retries; that's a different error contract from workflow nodes.
- n8n-binary-and-data — file/binary operations are fallible too; wire their error outputs like any network node.
- n8n-workflow-patterns——Webhook/API和定时模式是错误处理的应用场景。使用该技能构建整体结构;使用本技能强化可靠性。
- n8n-node-configuration——/
onError属于节点配置;NODE_FAMILY_GOTCHAS.md深入介绍了Webhook/Respond节点的状态码陷阱。retryOnFail - n8n-validation-expert——半配置的错误输出(两步中缺失一步)属于连接/配置审计项,而非验证错误。本技能提供修复方案。
- n8n-expression-syntax——表达式驱动的和告警消息表达式依赖正确的
Response Code语法及{{ }}访问方式。$json.error - n8n-code-javascript / n8n-code-python——如果在Code节点内捕获错误,请谨慎决定:重新抛出以使用错误输出,或处理后继续运行。不要返回错误格式的数据并假装成功。
- n8n-code-tool——Agent的Code Tool会将抛出的错误返回给LLM,LLM会进行重试;这与工作流节点的错误协议不同。
- n8n-binary-and-data——文件/二进制操作也可能出错;像配置网络节点一样配置它们的错误输出。
Quick reference checklist
快速参考检查清单
For an API / webhook workflow:
- Webhook trigger uses
responseMode: "responseNode" - Input validated upstream → 4xx Respond (schema validator or IF)
- Every fallible node has and
onError: "continueErrorOutput"wiredmain[1] - Network nodes have
retryOnFail: true, maxTries: 3, waitBetweenTries: 5000 - Error path ends at a Respond with an explicit 4xx/5xx
responseCode - Status code matches cause (4xx caller, 5xx you)
- Error body is — no stack traces, SQL, or tokens
{ error, message } - Verified with : both
n8n_get_workflowandonErrorpresent on each fallible nodemain[1]
For an unattended (scheduled/cron/queue) workflow:
- Network nodes have configured
retryOnFail - An Error Trigger workflow exists (capture → notify, optional retry)
- The error workflow notifies on a different channel + has a fallback (recursion trap)
- The error-workflow setting is assigned in the n8n UI (MCP can't do it — remind the user)
Remember: the default is silence. Error handling is two moves — make the failure route (per-node + wired output, or a catch-all error workflow) and make it speak (a status code and body that tell the truth). Half a move is worse than none, because it looks done.
onError对于API / Webhook工作流:
- Webhook触发器使用
responseMode: "responseNode" - 输入在上游验证 → 返回4xx响应(模式验证器或IF)
- 每个可能出错的节点都设置了且
onError: "continueErrorOutput"已连接main[1] - 网络节点配置了
retryOnFail: true, maxTries: 3, waitBetweenTries: 5000 - 错误路径以显式设置4xx/5xx 的Respond节点结束
responseCode - 状态码与故障原因匹配(4xx为调用方问题,5xx为我方问题)
- 错误体格式为——无堆栈跟踪、SQL或令牌
{ error, message } - 使用验证:每个可能出错的节点均同时配置了
n8n_get_workflow和onErrormain[1]
对于无人值守(定时/计划任务/队列)工作流:
- 网络节点配置了
retryOnFail - 存在Error Trigger工作流(捕获 → 通知,可选重试)
- 错误处理工作流使用不同的通知渠道 + 兜底方案(避免递归陷阱)
- 在n8n UI中分配了错误处理工作流(MCP无法完成——提醒用户)
记住:默认行为是静默。错误处理需要两步——让故障能够路由(节点级+已连接输出,或兜底错误处理工作流),并让故障能够传递有效信息(如实反映情况的状态码和响应体)。仅完成一半还不如不做,因为看起来已经配置完成,但实际无效。
onError