关于 n8n-error-handling
面向构建无人值守及 API 工作流的 n8n 用户,介绍如何让失败可见、可恢复并在适当情况下自动重试。重点涵盖节点错误输出与工作流级错误工作流。
主要功能
- 01配置节点级错误输出
- 02设置网络节点重试
- 03构建工作流级错误捕获
- 04按原因返回 HTTP 状态码
- 05校验输入并生成结构化错误
适用场景
- 01为 Webhook API 返回恰当错误
- 02在定时任务失败时发送告警
- 03从暂时性网络故障中恢复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ czlonkowski/ n8n-mcp --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。
先实现自我修复:配置 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(或下文的 schema 验证器)进行分支,并直接返回 400/401/403/404。错误输出用于处理意外故障(5xx)。
responseCode默认值为 200——即使在错误分支上也是如此。 这是一个容易忽视的陷阱(参阅 RESPONSE_SHAPES.md 和 n8n-node-configuration NODE_FAMILY_GOTCHAS.md):错误分支返回 200 和错误正文时,调用方的 HTTP 客户端会将其视为成功,因此不会触发错误处理。请在每个 Respond 节点上显式设置responseCode。
输入验证:Set 节点 schema 验证器
对于任何需要验证结构化输入的端点,都应在单个 Set 节点中通过 IIFE 执行检查,而不是针对每个字段串联 IF/Switch 节点。一个节点即可验证整个载荷,并返回 { valid, validationError, details, requiredSchema };再通过 IF 根据 valid 进行分支:有效时进入你的逻辑(200),无效时进入 400 Respond,并回显 schema,让调用方能够自行修正。这种方式也比在 Code 节点中使用递归验证器并搭配子工作流快得多。完整模式、约束说明和表达式转义注意事项请参阅 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 来自错误输出(“我们尝试了,但失败了”)。
**使用一个 Respond,通过表达式设置代码。**如果错误路径的差异仅在于数字和消息(正文结构和标头相同),就不要通过 Switch 分流到 N 个 Respond 节点。Respond 节点的 Response Code 和正文都支持表达式——直接内联计算代码:
// 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;
})() }}
只有在路径的结构不同时(标头、正文结构不同,或需要重定向),才使用 Switch + 多个 Respond。同一结构只更改数字时,使用一个由表达式驱动的 Respond 即可。
默认响应格式为 { "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 → Set (build alert from execution + error) → Slack/email (post to #incidents)
一条好的警报应包含工作流名称、指向编辑器和失败执行记录的链接、失败节点名称,以及真实的错误消息(而不是“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 |
错误分支以 {error} 正文返回 200 | 调用方客户端会认为操作成功;其错误处理逻辑永远不会触发 | 在错误 Respond 中将 responseCode 显式设为 4xx/5xx |
所有情况都返回同一个 500 internal_error | 调用方无法区分输入错误和服务中断 | 将原因映射到状态码(调用方问题用 4xx,你的问题用 5xx) |
| 在 Code 节点中捕获错误并将其作为数据返回 | 下游会处理形似错误的数据并继续执行 | 让错误抛出;使用 onError: "continueErrorOutput" 并接好对应路径 |
网络节点未设置 retryOnFail | 每次临时 429/网络抖动都会表现为 5xx;告警会因噪声触发 | retryOnFail: true, maxTries: 3, waitBetweenTries: 5000 |
| Switch → 多个仅状态码不同的 Respond | 本来一个 Respond 就够,却用了 5 个节点 | 在单个由表达式驱动的 Respond 中内联计算状态码 |
| 无人值守工作流没有错误工作流 | 真正发生故障时不会有任何处理 | 创建 Error Trigger 工作流,并在 UI 中为其指定错误工作流 |
| 错误工作流通知工作流监控使用的同一个频道 | 频道宕机 → 错误工作流也失败 → 错误消失 | 使用不同的频道,并添加 Data Table 备用方案 |
将 $json.error(堆栈/SQL/令牌)泄露到响应中 | 向调用方/攻击者暴露内部信息 | 私下记录日志,返回经过净化的消息 |
参考文件
| 文件 | 何时阅读 |
|---|---|
| NODE_ERROR_OUTPUTS.md | 为单个可能失败的节点接线其专属错误输出时 |
| API_WORKFLOWS.md | 构建/审查 webhook → Respond 工作流时,包括架构验证器 |
| 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 — 由表达式驱动的
Response Code和告警消息表达式依赖正确的{{ }}语法以及对$json.error的访问。 - n8n-code-javascript / n8n-code-python — 如果你在 Code 节点内部捕获错误,请有意决定:重新抛出以使用错误输出,还是处理后继续。不要返回形似错误的数据,却假装执行成功。
- n8n-code-tool — Agent 的 Code Tool 会将抛出的错误传回大模型,由大模型重试;这与工作流节点使用的是不同的错误契约。
- n8n-binary-and-data — 文件/二进制操作同样可能失败;像网络节点一样接好它们的错误输出。
快速参考清单
对于API / webhook工作流:
- Webhook 触发器使用
responseMode: "responseNode" - 在上游验证输入 → 由 Respond 返回 4xx(使用 schema validator 或 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]
对于无人值守(定时 / cron / 队列)工作流:
- 网络节点配置了
retryOnFail - 已创建 Error Trigger 工作流(捕获 → 通知,可选重试)
- 错误工作流通过不同的渠道发送通知,并设置了备用方案(避免递归陷阱)
- 已在 n8n UI 中分配错误工作流设置(MCP 无法完成此操作——提醒用户)
请记住:默认情况是静默。错误处理分两步——让失败能够流转(为每个节点设置 onError 并连接输出,或使用全捕获错误工作流),再让它发声(用状态码和正文如实说明情况)。只做一步比不做更糟,因为看起来像是已经处理好了。