关于 n8n-error-handling
这份参考技能介绍如何为 n8n 工作流设计错误处理,尤其适用于无人值守的 Webhook、定时任务、队列和 Agent 工具。内容涵盖节点错误输出、重试、输入验证和 HTTP 响应,并指出可能绕过错误分支的失败情形。
主要功能
- 01配置节点级错误输出路由
- 02设置工作流级兜底处理
- 03重试暂时性故障
- 04处理 Webhook 验证与状态码
- 05识别静默失败路径
适用场景
- 01为 Webhook API 返回结构化错误
- 02监控无人值守工作流故障
- 03区分无效输入与服务错误
- 04检查错误输出连线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ czlonkowski/ n8n-skills --skill n8n-error-handling安装后在 Claude Code 中输入/n8n-error-handling确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
API_WORKFLOWS.mdERROR_WORKFLOWS.mdNODE_ERROR_OUTPUTS.mdREADME.mdRESPONSE_SHAPES.md详细介绍
n8n 错误处理
默认情况下,n8n 节点抛出错误时,整个工作流都会停止。如果是你正在查看的交互式运行,这没问题——你会看到红色节点并修复它。但对于任何无人值守的场景(Webhook API、cron 任务、队列工作进程、Agent 工具),这就不是正确的默认设置:调用方会收到超时或空白的 500 响应,运维人员不会收到告警,最后只知道“集成突然停止工作了”,却没有日志,也无从查起。
本技能旨在让失败清晰可见、结构化且可恢复——理想情况下还能实现自愈,这样瞬时故障根本不会影响到人工处理。
以下两个做法可以避免大多数静默失败:
- 按节点设置错误输出——节点失败时,错误会沿着你控制的第二个输出传递,而不是直接终止运行。
- 工作流级错误工作流——捕获所有未经过节点级处理的错误(超时、节点间崩溃、未连接输出的失败)。
哪些情况下确实需要错误处理
| 工作流类型 | 错误处理要求 |
|---|---|
Webhook / API(任何包含 Respond to Webhook 的工作流) | 必需。 为每个可能失败的节点连接错误输出;状态码应与失败原因匹配。 |
| 定时任务 / cron / 队列工作进程 / Agent 工具(无人值守) | 必需。 设置工作流级错误工作流,并在网络节点上启用 retryOnFail。 |
| 由你亲自运行并监看的内部一次性任务 | 可选。 默认的 onError: "stopWorkflow" 即可——你会看到红色节点,然后重新运行。 |
判断标准是:如果输出会被你以外的任何人看到——下游系统、最终用户或值班工程师——就必须处理失败,而不能直接丢弃。如果只有你在监看,失败的代价只是“我发现后重新运行”,那么宽松一些也没问题。
最常见的静默陷阱:按节点设置错误输出需要分两步完成
这是 n8n 工作流“处理”错误时最常见的静默丢弃错误的方式。要将节点失败路由到处理器,需要进行两项更改;只做一项看起来像是配置完整,实际却会出问题:
- 在节点上设置
onError: "continueErrorOutput"。这会创建第二个输出。若不设置,无论你连接了什么,main[1]都不存在。 - 连接错误输出(
connections.<node>.main[1],即sourceIndex: 1),将其连接到实际的处理器。若没有目标节点,错误数据就会被丢弃。
只完成其中一项,就会遇到以下故障情况:
| 你的操作 | 运行时的结果 |
|---|---|
设置了 onError,但未连接错误输出 | 错误数据会被静默丢弃。下游不会运行。仪表板会显示运行成功。这是最糟糕的情况——错误可能完全没有记录。 |
已连接错误输出,但未设置 onError | 该输出不会触发;处理器无法访问。发生失败时,工作流只会停止(默认的 stopWorkflow)。 |
| 两项都完成 | 失败会沿着 main[1] 路由到你的处理器。✅ |
使用 n8n_update_partial_workflow 同时完成这两项
// 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 是成功路径,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。
错误输出永远捕获不到的失败
即使错误输出配置正确,仍会漏掉整类失败。在 n8n 2.38.5 上验证:
| 失败 | 使用 continueErrorOutput | 使用 continueRegularOutput |
|---|---|---|
{{ }} 内部的 JS 错误(缺失路径导致的 TypeError、对错误输入执行 JSON.parse、抛出的 Error、JMESPath 语法错误) | 不会触发任何输出;字段为 null,项目会走成功路径 | 同左 |
Python Code 节点在运行前被拒绝(导入被拦截、dunder 访问:Security violations detected)或返回格式错误(逐项模式下返回列表) | 节点标记为失败,但未更改的输入项目会从成功输出离开;main[1] 仍为空,执行状态显示成功 | 未更改的输入项目会直接传递 |
代码运行时发生异常(raise/throw、KeyError、NameError) | 路由到 main[1],格式为 { error } ✅ | 主输出上出现 { error } 项目 |
因此,仅靠错误分支无法防护以下情况:
- 检查数据,而不只是检查节点。 在输出结果很重要的转换节点之后,检查生成的字段是否存在(使用 IF/Filter 检查
{{ $json.total !== undefined && $json.total !== null }}),并自行将缺失情况发送到错误路径。 - 测试运行并查看节点状态和输出值。 Python 示例显示,执行状态为绿色,但其中有红色节点。表达式示例则只显示
null。 - 根本原因与修复方法:n8n-expression-syntax(调试)和 n8n-code-python(错误与
onError)。
优先实现自我修复:配置 retryOnFail,再连接错误路径
在构建错误分支之前,先处理瞬时故障,避免它们进入这些分支。对于任何调用网络服务的节点——HTTP Request、通信服务(Gmail/Slack/Discord)、数据库、AI 节点、第三方集成——设置节点级重试:
{ type: "updateNode", nodeName: "HTTP Request",
changes: {
retryOnFail: true,
maxTries: 3,
waitBetweenTries: 5000 // ms
} }
为什么要优先处理:429 或短暂的上游故障会触发重试,通常能自行恢复。这样,错误输出只会在真正持续存在的故障时触发,因此 5xx 响应和轮值告警反映的是实际问题,而不是噪声。
需要了解的引擎限制:任何错误都会触发重试(无法按状态码筛选),maxTries 上限为 5,waitBetweenTries 上限为 5000ms——因此 5000 既是最大值,也是合理的默认值。节点特定说明请参阅 n8n-node-configuration(NODE_FAMILY_GOTCHAS.md)。
API 工作流:标准结构
由 webhook 触发并向调用方响应的工作流有一条高于其他一切的规则:不能有悬空分支。每条路径——成功路径和每条错误路径——都必须以 Respond to Webhook 结束,否则调用方会一直等待,直到超时。
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
以下三点是实现该结构的关键:
- 将多个分支汇入同一个错误响应节点。 多个可能失败的节点可以将其
main[1]路由到同一个Respond节点,保持流程图清晰易读。 - 验证失败(4xx)应在上游检查,而不是通过错误输出处理。 缺少字段并不意味着节点崩溃——这是有明确预期响应的正常情况。使用 IF/Switch(或下方的逐字段验证器)对此进行分支处理,并直接返回 400/401/403/404。错误输出用于处理意外故障(5xx)。
- 即使在错误分支上,
options.responseCode的默认值也是 200。 这是一个隐蔽陷阱(参见 RESPONSE_SHAPES.md 和 n8n-node-configuration NODE_FAMILY_GOTCHAS.md):错误分支返回 200 和错误正文时,调用方的 HTTP 客户端会将其视为成功,因此不会触发错误处理。请在每个 Respond 节点上显式设置responseCode。
输入验证:画布上的条件
将检查放在一个 IF 节点中:每条规则对应一个布尔条件(is true),组合方式为 and。为 true → 执行你的逻辑(200);为 false → 返回一个 400 响应,并使用静态的 validation_error 响应体回显预期的 schema,以便调用方自行修正。当调用方需要逐字段消息时(例如 LLM 客户端、公共 API),用一个 Code 节点替换 IF 节点,让它遍历 schema 列表并返回 { valid, validationError, details, requiredSchema },同时附上说明 Why Code: IF considered — per-field messages need a loop over the schema。这两种模式、约束速查表和转义规则都在 API_WORKFLOWS.md 中。
响应格式:将原因映射到状态码
返回 text/plain "Internal Server Error" 的 5xx 响应,从技术上说是错误响应,但实际上毫无用处。并非每种故障都会返回 5xx。根据请求失败的原因匹配状态码,因为调用方会据此分支处理:他们的监控会针对 5xx(你的问题)触发警报,但不会针对 4xx(他们的问题);而 5xx 表示“重试”,4xx 则表示“不要重试”。
**常见错误:**将所有情况——包括输入错误——都接到同一个 Respond 节点,返回 500 internal_error。这样一来,调用方无法区分自身的错误与服务中断,你的错误率也无法区分真实故障与客户端噪声。
| 原因 | 状态码 | 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 从错误输出中产生(“我们尝试了,但出错了”)。
**使用 Switch 按原因路由;每个状态码对应一个静态 Respond。**从错误中选择状态码属于路由工作。使用 Switch 根据错误消息进行分类,然后将每个输出连接到一个 Respond,并设置固定的 options.responseCode 和固定的响应体:
error outputs (main[1]) ─→ Switch "Classify error" (on $json.error.message)
├─ contains "INVALID_ID" → Respond 400 validation_error
├─ contains "429" → Respond 429 rate_limit_exceeded
├─ contains "timeout" → Respond 504 upstream_timeout
└─ fallback (extra output) → Respond 500 internal_error
画布上会显示每个状态码,添加状态码也就是添加一条规则。将 Switch 的 options.fallbackOutput 设置为 extra,否则未匹配的错误会被丢弃,调用方将一直等待。只有两种结果时,可以在 Response Code 字段中使用一个简短的三元表达式,让单个 Respond 返回计算得出的状态码。Switch JSON 见 API_WORKFLOWS.md。
默认响应封装为 { "error": "<code>", "message": "<human text>" }——HTTP 状态码已经表明请求是成功还是失败,因此无需添加 ok: false 标记。切勿泄露内部信息(堆栈跟踪、SQL、上游响应体、令牌)到响应中——私下记录这些信息,并返回经过净化的消息。关联 ID、retry_after、验证 details 以及完整的禁止泄露清单见 RESPONSE_SHAPES.md。
工作流级错误工作流(兜底处理)
节点输出负责处理你预料到、且记得接线的节点上发生的失败。错误工作流则会捕获其他所有情况:忘记接线的节点、节点之间发生的崩溃、整个工作流超时、触发器失败。对于无人值守的工作流,它就像一道安全网,能把“它悄无声息地停止了”变成“收到了一条警报”。
将它构建为一个独立的工作流,并以 Error Trigger 节点作为起点。n8n 会在失败时将上下文传给它:
{
"execution": { "id": "...", "url": "...", "lastNodeExecuted": "Fetch order",
"error": { "name": "NodeApiError", "message": "...", "timestamp": 1715000000000 } },
"workflow": { "id": "...", "name": "Sync Stripe customers" }
}
最简版本——捕获 → 通知:
Error Trigger → Slack/email (post to #incidents; the alert text is built with expressions in the message field)
一条好的警报应包含工作流名称、指向编辑器和失败执行记录的链接、失败节点名称,以及真实的错误消息(而不是“Workflow failed”)。字段表达式,以及“通过 n8n 节点获取失败输入”的可选升级方案,见 ERROR_WORKFLOWS.md。
请特别留意以下两个陷阱:
- 递归陷阱。 如果错误工作流通过 Slack 发送通知,而 Slack 恰好不可用,错误工作流也会失败——原始错误也会随之消失。请使用与你监控的工作流不同的渠道发送通知(大多数工作流通过 Slack 告警 → 错误工作流使用电子邮件),并添加备用方案(写入 Data Table),这样即使通知失败,也会留下记录。
- 已“处理”的错误不会继续向上冒泡。 如果某个节点的错误输出连接到了一个会丢弃数据的空操作节点,n8n 就会将该错误视为已处理,错误工作流也不会触发。只有在确实要对错误进行处理时,才按节点捕获错误。
**社区 MCP 无法执行的操作:**设置错误工作流(实例默认设置或按工作流覆盖)属于 n8n UI 设置——Workflow Settings → Error Workflow。没有可用于此操作的 MCP 工具。使用 MCP 构建错误工作流后,告知用户要在哪个 UI 位置进行设置,并提醒他们为每个无人值守的工作流重复此操作(或设置实例默认值)。
社区 MCP 不提供的功能
| 想要执行的操作 | 实际情况 |
|---|---|
| 设置工作流的 Error Workflow 选项 | 仅限 UI(Workflow Settings → Error Workflow)。没有 MCP 工具。构建工作流后,告知用户相应的 UI 操作步骤。 |
| 切换其他工作流设置(Save Execution Data、时区、超时、调用者策略) | 仅限 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)。
反模式
| 反模式 | 问题所在 | 修复方法 |
|---|---|---|
设置了 onError,但未连接错误输出 | 错误被悄悄丢弃;运行显示为成功 | 将 sourceIndex: 1 连接到实际处理程序,或将 onError 恢复为 stopWorkflow,以便明确报错 |
错误输出已连接,但未设置 onError | 插槽永远不会触发;处理程序无法访问;工作流在失败时停止 | 设置 onError: "continueErrorOutput" |
| Webhook → 处理 → 响应,没有错误分支 | 调用方收到超时或 n8n 的通用 500 错误 | 将每个可能失败的节点的错误输出连接到 Respond |
错误分支返回 200 和 {error} 正文 | 调用方的客户端会将其视为成功;错误处理不会触发 | 在错误分支的 Respond 中明确将 options.responseCode 设置为 4xx/5xx |
所有情况都返回一个 500 internal_error | 调用方无法分辨是输入有误还是服务中断 | 根据原因映射状态码(调用方的问题用 4xx,你的问题用 5xx) |
| 在 Code 节点中捕获错误并将其作为数据返回 | 下游会处理错误格式的数据并继续运行 | 让错误抛出;使用 onError: "continueErrorOutput" 并连接对应路径 |
网络节点未设置 retryOnFail | 每次短暂的 429/网络抖动都会引发 5xx;无关紧要的警报也会触发 | retryOnFail: true, maxTries: 3, waitBetweenTries: 5000 |
| 在 Respond 的 Response Code 或正文中编写函数体 | 路由逻辑隐藏在字段中,运行时错误会变成静默的 null | 根据原因进行分支判断 → 每个状态码使用一个静态 Respond |
| 无人值守的工作流没有错误工作流 | 真正发生的故障无人处理 | 构建 Error Trigger 工作流,并在 UI 中为其分配工作流 |
| 错误工作流通知与其监控的工作流使用同一个频道 | 频道宕机 → 错误工作流也失败 → 错误消失 | 使用不同的频道,并增加 Data Table 作为后备 |
在响应中泄露 $json.error(堆栈/SQL/令牌) | 向调用方/攻击者暴露内部信息 | 私下记录日志,返回经过清理的消息 |
参考文件
| 文件 | 阅读时机 |
|---|---|
| NODE_ERROR_OUTPUTS.md | 为单个可能失败的节点连接节点级错误输出时 |
| API_WORKFLOWS.md | 构建/审查 Webhook → Respond 工作流时,包括输入验证(IF 条件、逐字段 Code 验证器) |
| RESPONSE_SHAPES.md | 定义响应正文约定、状态码以及不可泄露的信息时 |
| ERROR_WORKFLOWS.md | 为无人值守的工作流设置工作流级兜底处理时 |
与其他技能的集成
- n8n-workflow-patterns — Webhook/API 和定时模式是错误处理的应用场景。使用它来确定整体结构;使用此技能来强化处理。
- n8n-node-configuration —
onError/retryOnFail属于节点配置;NODE_FAMILY_GOTCHAS.md 深入介绍了 Webhook/Respond 响应码陷阱。 - n8n-validation-expert — 错误输出只连接了一半(两项配置中缺少一项)属于连接/配置审计项,而非验证错误。此技能提供修复方法。
- n8n-expression-syntax —
$json.error.message上的 Switch 条件和警报消息表达式依赖正确的{{ }}语法及对$json.error的访问。 - n8n-code-javascript / n8n-code-python — 如果在 Code 节点内部捕获错误,请慎重决定:重新抛出以使用错误输出,或处理错误后继续运行。不要返回错误格式的数据,却假装执行成功。
- n8n-code-tool — Agent 的 Code Tool 会将抛出的错误返回给大模型,然后由大模型重试;这与工作流节点的错误约定不同。
- n8n-binary-and-data — 文件/二进制操作也可能失败;像连接任何网络节点一样连接其错误输出。
快速参考清单
对于 API / webhook 工作流:
- Webhook 触发器使用
responseMode: "responseNode" - 上游验证输入 → 使用 4xx 响应(IF 条件,或逐字段 Code 验证器)
- 每个可能失败的节点都设置
onError: "continueErrorOutput",并且连接了main[1] - 网络节点设置
retryOnFail: true, maxTries: 3, waitBetweenTries: 5000 - 错误路径以一个 Respond 节点结束,并在
options.responseCode中明确设置 4xx/5xx(顶层的responseCode会被忽略) - 状态码与原因相符(调用方导致的问题用 4xx,你导致的问题用 5xx)
- 错误正文为
{ error, message },不含堆栈跟踪、SQL 或令牌 - 使用
n8n_get_workflow验证:每个可能失败的节点都同时包含onError和main[1]
对于无人值守(定时/cron/队列)工作流:
- 网络节点已配置
retryOnFail - 已创建 Error Trigger 工作流(捕获 → 通知,可选重试)
- 错误工作流通过不同的渠道发送通知,并设有备用方案(防止递归)
- 已在 n8n UI 中分配错误工作流设置(MCP 无法完成此操作——请提醒用户)
请记住:默认情况是静默。错误处理分两步——让故障流转(为每个节点设置 onError 并连接输出,或创建兜底错误工作流),再让它发声(通过状态码和正文如实说明情况)。只做一半比什么都不做更糟,因为看起来像是已经处理好了。