n8n-error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

n8n 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 shapeError handling posture
Webhook / API (anything with
Respond to Webhook
)
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
retryOnFail
on network nodes.
Internal one-off you run and watch yourselfOptional. Default
onError: "stopWorkflow"
is fine — you'll see the red node and re-run.
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(包含
Respond to Webhook
的场景)
必须配置。每个可能出错的节点的错误输出都需配置连接,状态码需与故障原因匹配。
定时/计划任务 / 队列 worker / Agent工具(无人值守)必须配置。需设置工作流级错误处理工作流,同时在网络节点上启用
retryOnFail
你自行运行并监控的内部一次性工作流可选。默认的
onError: "stopWorkflow"
即可——你会看到红色节点并重新运行。
判断标准:如果除你之外的任何人会看到输出(如下游系统、终端用户、值班工程师),则必须处理故障,而非忽略。如果只有你监控,且故障代价仅为“我发现后重新运行”,则可以放宽要求。

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:
  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.
  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.
Get one without the other and you hit a failure mode:
What you didWhat happens at runtime
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.
Error output wired,
onError
not set
The slot never fires; the handler is unreachable. On failure the workflow just halts (default
stopWorkflow
).
Both doneFailure routes down
main[1]
to your handler. ✅
这是n8n工作流“看似处理错误但实际静默吞掉错误”的最常见原因。将节点故障路由到处理程序需要两步操作,仅完成其中一步看似配置完成,但实际行为异常:
  1. 在节点上设置
    onError: "continueErrorOutput"
    ——这会创建第二个输出。如果不设置,无论你如何连接,
    main[1]
    都不会存在。
  2. 连接该错误输出
    connections.<node>.main[1]
    ,即
    sourceIndex: 1
    )到实际的处理程序。如果没有目标节点,错误数据会被丢弃。
仅完成其中一步会导致以下故障模式:
操作内容运行时表现
设置了
onError
但未连接错误输出
错误数据被静默丢弃。下游节点不会触发。控制台显示运行成功。最糟情况——无任何错误日志。
连接了错误输出但未设置
onError
该输出槽永远不会触发;处理程序无法访问。故障发生时工作流直接停止(默认
stopWorkflow
)。
两步均完成故障会通过
main[1]
路由到你的处理程序。 ✅

Doing both with
n8n_update_partial_workflow

使用
n8n_update_partial_workflow
完成两步配置

javascript
// 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: 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
.)
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:
  • The node's
    onError
    is
    "continueErrorOutput"
    .
  • connections["HTTP Request"].main[1]
    contains your handler.
Valid
onError
values:
ValueEffect
"stopWorkflow"
(default)
Error halts the whole workflow.
"continueRegularOutput"
Error item flows out the normal output. Rare, usually wrong — downstream gets error-shaped data and keeps going.
"continueErrorOutput"
Error item flows out the separate error output (
main[1]
). The one you wire.
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: 0
是成功路径,
sourceIndex: 1
是错误路径。(对于IF节点,别名
branch: "true"
/
"false"
对应索引0/1;对于通用可能出错的节点,使用显式的
sourceIndex: 1
。)
务必验证。这种陷阱不会在
validate_workflow
中暴露——半配置的错误输出会验证通过。使用
n8n_get_workflow
拉取工作流并确认两步均已完成
  • 节点的
    onError
    设置为
    "continueErrorOutput"
  • connections["HTTP Request"].main[1]
    包含你的处理程序。
onError
的有效值:
效果
"stopWorkflow"
(默认)
错误会终止整个工作流。
"continueRegularOutput"
错误项会从正常输出流出。很少使用,通常不合适——下游会收到错误格式的数据并继续运行。
"continueErrorOutput"
错误项会从独立的错误输出(
main[1]
)流出。这是你需要连接的输出。
完整的故障模式目录、扇入/扇出结构及验证方法:详见NODE_ERROR_OUTPUTS.md

Self-healing first:
retryOnFail
before you wire error paths

优先实现自愈:配置错误分支前先启用
retryOnFail

Before 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),
maxTries
caps at 5, and
waitBetweenTries
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.

在构建错误分支之前,先处理临时故障,使其不会触发错误分支。对于任何调用网络服务的节点——HTTP Request、通信节点(Gmail/Slack/Discord)、数据库、AI节点、第三方集成——设置节点级重试:
javascript
{ type: "updateNode", nodeName: "HTTP Request",
  changes: {
    retryOnFail: true,
    maxTries: 3,
    waitBetweenTries: 5000   // 毫秒
  } }
为什么要优先配置:429(请求过多)或上游短暂故障会自动重试,通常会成功。错误输出仅在真实的、持续性的故障时触发——这样你的5xx响应和值班告警反映的是实际问题,而非噪音。
需要了解的引擎限制:重试会在任何错误时触发(无按状态码过滤的选项),
maxTries
上限为5,
waitBetweenTries
上限为5000毫秒——因此5000既是最大值,也是合理的默认值。节点特定说明详见n8n-node-configuration(NODE_FAMILY_GOTCHAS.md)。

API 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
Respond to Webhook
, or the caller sits there until it times out.
Webhook (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 / notify
Three things make this work:
  1. Fan-in to one error responder. Many fallible nodes can route their
    main[1]
    to a single
    Respond
    node. Keeps the graph readable.
  2. 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).
  3. responseCode
    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
    responseCode
    explicitly on every Respond node.
Webhook触发的工作流在响应调用方时有一个核心规则:不能有未终止的分支。每个路径——成功路径和所有错误路径——都必须以
Respond to Webhook
结束,否则调用方会一直等待直到超时。
Webhook (responseMode: "responseNode")
  ├── 验证输入 → 处理 → Respond (200, 响应体)
  └──(任何可能出错的节点的错误输出 → sourceIndex 1)
            → Respond (4xx/5xx, 结构化错误体)
            → 可选:私下记录完整错误 / 发送通知
实现该结构的三个关键点:
  1. 所有错误路径汇聚到一个错误响应节点。多个可能出错的节点可以将其
    main[1]
    路由到同一个
    Respond
    节点。保持流程图可读性。
  2. 验证失败(4xx)需在上游检查,而非通过错误输出。缺失字段不是节点崩溃——这是预期结果,有已知的响应。使用IF/Switch(或下方的模式验证器)进行分支,直接返回400/401/403/404。错误输出用于处理意外故障(5xx)。
  3. responseCode
    默认值为200——即使在错误分支中
    。这是另一个静默陷阱(详见RESPONSE_SHAPES.md和n8n-node-configuration NODE_FAMILY_GOTCHAS.md):返回200状态码但包含错误体的错误分支,在调用方的HTTP客户端看来是成功的,因此他们的错误处理逻辑不会触发。需在每个Respond节点上显式设置
    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
{ valid, validationError, details, requiredSchema }
, and an IF branches on
valid
→ 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.

对于任何需要结构化输入验证的端点,可在单个Set节点内使用立即执行函数(IIFE)完成检查,而非为每个字段设置一系列IF/Switch节点。一个节点即可验证整个负载,返回
{ valid, validationError, details, requiredSchema }
,然后通过IF节点根据
valid
分支到业务逻辑(200响应)或返回400响应并回显模式,以便调用方自行修正。这比在Code节点+子工作流中使用递归验证器快得多。完整模式、约束指南及表达式转义陷阱详见API_WORKFLOWS.md

Response shapes: map cause → status code

响应格式:根据故障原因映射状态码

A 5xx with
text/plain "Internal Server Error"
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".
The common mistake: wiring everything — including bad input — to one
Respond
that returns 500
internal_error
. Now the caller can't tell their bug from your outage, and your error rates can't separate real incidents from client noise.
CauseStatus
error
code
Where it's handled
Required field missing / wrong type400
validation_error
Upstream check (schema validator / IF), not error output
Auth missing or invalid401
unauthorized
Upstream check
Authenticated but not allowed403
forbidden
Upstream check
Resource ID valid in request, absent in your data404
not_found
Branch on the lookup result, not its error
Conflicts with current state (duplicate, race)409
conflict
Detect with logic
Caller exceeded rate limit429
rate_limit_exceeded
Set
Retry-After
header
Node threw, cause unknown500
internal_error
Error output path
Third-party API returned an error502
upstream_error
Error output of the HTTP node
Can't process right now (downstream down)503
service_unavailable
Detect specific error, hint retry
Third-party API timed out504
upstream_timeout
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
Response Code
and body — compute the code inline:
javascript
// 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
{ "error": "<code>", "message": "<human text>" }
— the HTTP status already says success-vs-failure, so no
ok: false
flag. Never leak internals (stack traces, SQL, upstream bodies, tokens) into the response — log those privately, return a sanitized message. Correlation IDs,
retry_after
, validation
details
, and the full do-not-leak list are in RESPONSE_SHAPES.md.

返回5xx状态码+
text/plain "Internal Server Error"
在技术上是错误响应,但实际上毫无用处。并非所有故障都是5xx。状态码需与请求失败的原因匹配,因为调用方会根据状态码进行分支处理:他们的监控会针对5xx告警(你的问题),但不会针对4xx(他们的问题);5xx提示“可重试”,而4xx提示“请勿重试”。
常见错误:将所有故障——包括无效输入——都路由到一个返回500
internal_error
Respond
节点。这样调用方无法区分是他们的bug还是你的服务中断,你的错误率也无法区分真实事件与客户端噪音。
故障原因状态码
error
代码
处理位置
必填字段缺失 / 类型错误400
validation_error
上游检查(模式验证器 / IF),而非错误输出
认证信息缺失或无效401
unauthorized
上游检查
已认证但无权限403
forbidden
上游检查
请求中的资源ID有效,但你的数据中不存在404
not_found
根据查询结果分支,而非查询错误
与当前状态冲突(重复、竞态)409
conflict
通过逻辑检测
调用方超出速率限制429
rate_limit_exceeded
设置
Retry-After
节点抛出异常,原因未知500
internal_error
错误输出路径
第三方API返回错误502
upstream_error
HTTP节点的错误输出
当前无法处理(下游服务宕机)503
service_unavailable
检测特定错误,提示可重试
第三方API超时504
upstream_timeout
根据错误消息过滤的错误输出
因此存在两种不同的流程:4xx在执行工作前确定(IF/Switch + 专用Respond节点),5xx来自错误输出(“我们尝试了,但失败了”)。
单个Respond节点,通过表达式驱动状态码。当错误路径仅在状态码和消息上不同(响应体结构相同、头信息相同)时,无需通过Switch节点分支到N个Respond节点。Respond节点的
Response Code
和响应体均支持表达式——可内联计算状态码:
javascript
// 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节点即可。
默认响应格式为
{ "error": "<code>", "message": "<人类可读文本>" }
——HTTP状态码已表明成功或失败,因此无需
ok: false
标志。绝对不要泄露内部信息(堆栈跟踪、SQL语句、上游响应体、令牌)到响应中——私下记录这些信息,返回经过脱敏的消息。关联ID、
retry_after
、验证
details
及完整的禁止泄露列表详见RESPONSE_SHAPES.md

Workflow-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 doReality
Set a workflow's Error Workflow settingUI 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.
n8n_update_partial_workflow
has
updateSettings
, but the error-workflow assignment is not reliably exposed — confirm in the UI.
Enable instance-wide error logging (Sentry, server logs)Instance config, outside n8n workflows entirely.
What the MCP can do: build the error workflow, set
onError
/
retryOnFail
on nodes (
updateNode
/
patchNodeField
), wire error outputs (
addConnection
with
sourceIndex: 1
), validate (
validate_workflow
,
n8n_validate_workflow
), auto-fix common issues (
n8n_autofix_workflow
), test (
n8n_test_workflow
), and inspect failures (
n8n_executions
).

需求实际情况
设置工作流的错误处理工作流配置仅支持UI操作(工作流设置 → 错误处理工作流)。无MCP工具。构建工作流后,告知用户UI步骤。
切换其他工作流设置(保存执行数据、时区、超时、调用方策略)仅支持UI操作。
n8n_update_partial_workflow
updateSettings
,但错误处理工作流的分配无法可靠暴露——需在UI中确认。
启用实例级错误日志(Sentry、服务器日志)属于实例配置,完全在n8n工作流之外。
MCP可以实现的功能:构建错误处理工作流、在节点上设置
onError
/
retryOnFail
updateNode
/
patchNodeField
)、连接错误输出(
addConnection
搭配
sourceIndex: 1
)、验证(
validate_workflow
n8n_validate_workflow
)、自动修复常见问题(
n8n_autofix_workflow
)、测试(
n8n_test_workflow
)、检查故障(
n8n_executions
)。

Anti-patterns

反模式

Anti-patternWhat goes wrongFix
onError
set but error output unwired
Error silently discarded; run shows as succeededWire
sourceIndex: 1
to a real handler, or revert
onError
to
stopWorkflow
so it's loud
Error output wired but
onError
not set
Slot never fires; handler unreachable; workflow halts on failureSet
onError: "continueErrorOutput"
Webhook → process → respond, no error branchCaller gets a timeout or n8n's generic 500Wire every fallible node's error output to a Respond
Error branch returns 200 with an
{error}
body
Caller's client reads success; their error handling never firesSet
responseCode
to 4xx/5xx explicitly on error Responds
One 500
internal_error
for everything
Caller can't tell their bad input from your outageMap cause → status (4xx caller, 5xx you)
Catching errors in a Code node and returning them as dataDownstream processes error-shaped data and continuesLet it throw; use
onError: "continueErrorOutput"
+ wired path
Network node with no
retryOnFail
Every transient 429/blip surfaces as a 5xx; alerts fire on noise
retryOnFail: true, maxTries: 3, waitBetweenTries: 5000
Switch → N Responds differing only by status code5 nodes for what's one RespondCompute the code inline in one expression-driven Respond
Unattended workflow with no error workflowA genuine failure goes nowhereBuild an Error Trigger workflow + assign it in the UI
Error workflow notifies the same channel the workflows monitorChannel down → error workflow also fails → error vanishesUse a different channel + a Data Table fallback
Leaking
$json.error
(stack/SQL/tokens) into the response
Exposes internals to callers/attackersLog privately, return a sanitized message

反模式问题修复方案
设置了
onError
但未连接错误输出
错误被静默丢弃;运行显示为成功
sourceIndex: 1
连接到实际处理程序,或恢复
onError
stopWorkflow
使其清晰可见
连接了错误输出但未设置
onError
输出槽永远不会触发;处理程序无法访问;故障时工作流停止设置
onError: "continueErrorOutput"
Webhook → 处理 → 响应,但无错误分支调用方收到超时或n8n通用的500响应将每个可能出错的节点的错误输出连接到Respond节点
错误分支返回200状态码及
{error}
响应体
调用方客户端认为是成功;他们的错误处理逻辑不会触发在错误分支的Respond节点上显式设置4xx/5xx的
responseCode
所有故障都返回500
internal_error
调用方无法区分是他们的无效输入还是你的服务中断根据原因映射状态码(4xx为调用方问题,5xx为我方问题)
在Code节点中捕获错误并作为数据返回下游处理错误格式的数据并继续运行让错误抛出;使用
onError: "continueErrorOutput"
+ 已连接的路径
网络节点未配置
retryOnFail
每个临时的429/故障都会触发5xx响应;告警被噪音淹没设置
retryOnFail: true, maxTries: 3, waitBetweenTries: 5000
Switch → N个仅状态码不同的Respond节点用5个节点实现单个节点即可完成的功能在单个表达式驱动的Respond节点内联计算状态码
无人值守工作流未配置错误处理工作流真实故障无任何通知构建Error Trigger工作流 + 在UI中分配
错误处理工作流使用与受监控工作流相同的通知渠道渠道宕机 → 错误处理工作流也失败 → 错误消失使用不同的渠道 + Data Table兜底
$json.error
(堆栈/SQL/令牌)泄露到响应中
向调用方/攻击者暴露内部信息私下记录,返回脱敏消息

Reference files

参考文件

FileRead when
NODE_ERROR_OUTPUTS.mdWiring a per-node error output on individual fallible nodes
API_WORKFLOWS.mdBuilding/reviewing a webhook → Respond workflow, including the schema validator
RESPONSE_SHAPES.mdDefining response body conventions, status codes, and what not to leak
ERROR_WORKFLOWS.mdSetting 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
    onError
    /
    retryOnFail
    are node config; NODE_FAMILY_GOTCHAS.md covers the Webhook/Respond response-code traps in depth.
  • 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
    Response Code
    and the alert-message expressions rely on correct
    {{ }}
    syntax and
    $json.error
    access.
  • 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
    /
    retryOnFail
    属于节点配置;NODE_FAMILY_GOTCHAS.md深入介绍了Webhook/Respond节点的状态码陷阱。
  • 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
    onError: "continueErrorOutput"
    and
    main[1]
    wired
  • 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
    { error, message }
    — no stack traces, SQL, or tokens
  • Verified with
    n8n_get_workflow
    : both
    onError
    and
    main[1]
    present on each fallible node
For an unattended (scheduled/cron/queue) workflow:
  • Network nodes have
    retryOnFail
    configured
  • 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
onError
+ 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.
对于API / Webhook工作流:
  • Webhook触发器使用
    responseMode: "responseNode"
  • 输入在上游验证 → 返回4xx响应(模式验证器或IF)
  • 每个可能出错的节点都设置了
    onError: "continueErrorOutput"
    main[1]
    已连接
  • 网络节点配置了
    retryOnFail: true, maxTries: 3, waitBetweenTries: 5000
  • 错误路径以显式设置4xx/5xx
    responseCode
    的Respond节点结束
  • 状态码与故障原因匹配(4xx为调用方问题,5xx为我方问题)
  • 错误体格式为
    { error, message }
    ——无堆栈跟踪、SQL或令牌
  • 使用
    n8n_get_workflow
    验证:每个可能出错的节点均同时配置了
    onError
    main[1]
对于无人值守(定时/计划任务/队列)工作流:
  • 网络节点配置了
    retryOnFail
  • 存在Error Trigger工作流(捕获 → 通知,可选重试)
  • 错误处理工作流使用不同的通知渠道 + 兜底方案(避免递归陷阱)
  • 在n8n UI中分配了错误处理工作流(MCP无法完成——提醒用户)

记住:默认行为是静默。错误处理需要两步——让故障能够路由(节点级
onError
+已连接输出,或兜底错误处理工作流),并让故障能够传递有效信息(如实反映情况的状态码和响应体)。仅完成一半还不如不做,因为看起来已经配置完成,但实际无效。