Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Debug failed or wrong-output workflow executions using executions tools. Load when the user reports execution failures, unexpected node output, empty parameter values after a successful run, or a node showing a red or failed expression error.
.claude/skills/n8n-io-debugging-executions/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 25% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 184% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 43% | 0% |
Use this skill when debugging workflow execution failures or successful runs with wrong or empty values.
Re-run the failing path with executions(action="run") (or verify-built-workflow) and inspect the real result before responding. Do not restate that the workflow is "fixed", "verified", or "working", and do not attribute the reported failure to a test-harness artifact, stale state, or "it works in production" without a re-run against the failing path. Treat live signals as real: an execution error, partial coverage (nodesNotReached), an empty node, or a missing node is a real defect to investigate, not something to explain away. If you genuinely cannot re-run the failing path, say so plainly and name what is unconfirmed instead of repeating a success claim.
A published workflow runs the version that was published, not the draft you edit. Your save creates a draft, and the draft is not live until somebody publishes it. So a fix to a live workflow changes nothing in production on its own.
Two consequences when the user reports a live workflow failing:
executions(action="list", workflowId)returns workflowVersionId on every row, plus workflow.activeVersionId (the published version) and workflow.draftVersionId. Only a row whose workflowVersionId equals workflow.activeVersionId ran the published code. A run of a draft proves nothing about production. A workflow.draftVersionId different from workflow.activeVersionId means the latest changes, including any fix you just made, are not live.
draft or the published version. "Try it again" after a draft-only fix sends the user to the broken published version, and the fix looks like it failed.
Do not report a fix as live until the published version is the fixed one. Ask whether to publish it instead. After a publish, a new live run is what confirms the fix — an earlier execution ran the old version.
Use executions(action="run") with inputData matching the trigger's output shape — do not rebuild the workflow with a Manual Trigger. For trigger inputData shapes, read ${N8N_WORKSPACE_DIR}/knowledge-base/reference/trigger-input-data-shapes.md when a sandbox workspace is available.
executions(action="debug") already includes failedNode.resolvedParameters — start there. That bundle has parameters (raw, with expressions intact), resolved (substituted), failedExpressions (those that threw), and emptyResolutions (those that resolved to null/undefined/"" silently). The offending expression is usually visible without a follow-up call. Entries in either list tagged with reason: "unreconstructable-context" are NOT real bugs — they reference variables we don't reconstruct in replay ($vars, $secrets, $response, $request, $pageCount, $ai). The value existed at execution time; we just don't have it here.
executions(action="debug") tells you what the node received. It does not tell you whether your fix works. To learn that, run the node itself:
executions(action="run-step", workflowId, nodeName, reuseExecutionId=<the failed execution>)reuseExecutionId replays the data the node really received and re-runs only that node, so the fix meets the same input that broke it. This is the right first move whenever the user is debugging a read node that already failed a real execution: the node ran for real once already, and a mock-only check is what sends the user back for a second session.
A step run is a real run. The node uses the user's real credentials and reaches the user's real systems, on the user's real data. Check what the node does before you reach for run-step:
get, getAll, search, list, download, a GETHTTP Request), or a transform that touches nothing outside the workflow (Set, IF, Filter, Code without network or filesystem access). Run these.
create, update, upsert, delete,send, append, a non-GET HTTP Request). Running one sends the message, charges the card, or deletes the row — again, and for real. The user asked you to debug the node, not to perform its effect.
an un-asked write to the user's data is not.
For a write node, debug without running it: read the failed execution with debug, inspect the resolved parameters with get-resolved-node-parameters, and explain the fix. That is usually enough, because a write node's failures are nearly always in its input or its parameters, both of which you can see without sending anything.
If you genuinely cannot resolve it without a real run, say plainly what the node will do to the user's data, and let the user choose. The approval prompt alone is not consent: the user sees a node name, not "this posts to your #general channel".
"It already ran anyway" is not a reason. It holds only for a node that errored outright and changed nothing. A node that partly succeeded before it failed — a send that delivered some messages and then hit a rate limit — will deliver them again.
mockInputmockInput runs the node on items you supply and skips everything above it. This is a good way to study one node by itself, and a normal thing to do while debugging:
field, a zero or negative amount;
attempts are comparable;
Reach for it whenever the question is about the node. Use reuseExecutionId or a chain run when the question is about the workflow.
Keep the claim at the level of the evidence. A mocked run shows the node handles the input you gave it; it shows nothing about what the chain really produces. The result carries inputMode: "mocked" and a mockedNodeNames list — report the node's behaviour, not the workflow's.
Mocked input does not make a write node safe. The node still runs for real against the user's systems; only its input is invented, which makes the effect less predictable, not more.
When debug doesn't apply because nothing errored, call executions(action="get-resolved-node-parameters", executionId, nodeName) on the node whose output looks off — do this unprompted, don't ask the user for permission first. It's a cheap read-only inspection and the only reliable way to confirm whether an empty value came from an expression silently resolving to nullish. Check emptyResolutions first; most "this parameter is empty" cases are expressions resolving to null/undefined/"", not thrown errors.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 11,946 | 9,273 | -22% | 1 | 1 | 0% | 1,964 | 2,090 | +6% | 0 | 0 | — |
case-02 | fail→fail | 17,636 | 8,491 | -52% | 1 | 1 | 0% | 2,754 | 2,259 | -18% | 0 | 0 | — |
case-03 | fail→fail | 11,834 | 11,817 | -0% | 1 | 1 | 0% | 1,756 | 2,239 | +28% | 0 | 0 | — |
case-04 | pass→pass | 16,093 | 14,214 | -12% | 1 | 1 | 0% | 3,233 | 4,483 | +39% | 0 | 0 | — |
case-05 | pass→pass | 15,146 | 15,850 | +5% | 1 | 1 | 0% | 2,583 | 4,495 | +74% | 0 | 0 | — |
case-06 | pass→pass | 12,826 | 9,719 | -24% | 1 | 1 | 0% | 2,294 | 3,409 | +49% | 0 | 0 | — |
case-07 | fail→pass | 13,448 | 6,075 | -55% | 1 | 1 | 0% | 2,044 | 2,558 | +25% | 0 | 0 | — |
case-08 | fail→fail | 11,676 | 7,426 | -36% | 1 | 1 | 0% | 1,854 | 3,041 | +64% | 0 | 0 | — |
case-09 | fail→pass | 11,073 | 12,349 | +12% | 1 | 1 | 0% | 1,708 | 2,857 | +67% | 0 | 0 | — |
case-10 | fail→fail | 6,117 | 11,814 | +93% | 1 | 1 | 0% | 816 | 2,367 | +190% | 0 | 0 | — |
case-11 | fail→fail | 14,176 | 10,181 | -28% | 1 | 1 | 0% | 2,135 | 2,121 | -1% | 0 | 0 | — |
case-12 | fail→fail | 13,034 | 6,980 | -46% | 1 | 1 | 0% | 1,907 | 2,663 | +40% | 0 | 0 | — |
case-13 | fail→fail | 9,023 | 8,015 | -11% | 1 | 1 | 0% | 1,040 | 2,264 | +118% | 0 | 0 | — |
case-14 | pass→pass | 8,524 | 5,592 | -34% | 1 | 1 | 0% | 1,171 | 2,539 | +117% | 0 | 0 | — |
case-15 | pass→pass | 5,088 | 6,960 | +37% | 1 | 1 | 0% | 758 | 2,794 | +269% | 0 | 0 | — |
case-16 | pass→pass | 11,311 | 7,107 | -37% | 1 | 1 | 0% | 1,509 | 2,755 | +83% | 0 | 0 | — |
case-17 | fail→pass | 15,625 | 12,106 | -23% | 1 | 1 | 0% | 2,508 | 3,788 | +51% | 0 | 0 | — |
case-18 | fail→pass | 5,810 | 3,757 | -35% | 1 | 1 | 0% | 814 | 2,312 | +184% | 0 | 0 | — |
case-19 | pass→pass | 11,192 | 10,526 | -6% | 1 | 1 | 0% | 1,904 | 3,329 | +75% | 0 | 0 | — |
case-20 | pass→pass | 9,200 | 5,950 | -35% | 1 | 1 | 0% | 1,398 | 2,399 | +72% | 0 | 0 | — |
case-21 | fail→pass | 11,974 | 7,760 | -35% | 1 | 1 | 0% | 2,011 | 2,869 | +43% | 0 | 0 | — |
case-22 | fail→fail | 13,529 | 11,590 | -14% | 1 | 1 | 0% | 2,167 | 2,515 | +16% | 0 | 0 | — |
case-23 | pass→pass | 9,518 | 6,370 | -33% | 1 | 1 | 0% | 1,191 | 2,656 | +123% | 0 | 0 | — |
case-24 | pass→pass | 13,769 | 10,141 | -26% | 1 | 1 | 0% | 1,976 | 2,669 | +35% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted, and 17 counted toward the lift figure. The other 7 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +21 percentage points is the difference between those two pass rates over the 17 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.