This document explains how n8n handles partial workflow execution, error workflow triggers, and execution recovery. Partial execution allows running only a portion of a workflow from a specific node to a destination, reusing existing run data where possible. Error workflows provide a mechanism to handle and respond to workflow failures. Execution recovery ensures crashed or interrupted executions can be properly handled and resumed.
For information about the overall workflow execution engine and lifecycle, see Workflow Execution Lifecycle. For distributed execution and scaling, see Distributed Execution and Scaling.
Partial execution is the mechanism that allows n8n to execute only a subset of nodes in a workflow, typically when:
The core implementation is in WorkflowExecute.runPartialWorkflow2().
Run Data (IRunData): Stores the execution results of each node, indexed by node name and run index. This data persists across partial executions to avoid re-executing unchanged nodes.
Dirty Nodes: Nodes whose parameters, connections, or dependencies have changed since the last execution. These nodes and their descendants must be re-executed.
Pin Data (IPinData): Manually specified data that overrides node execution. When a node has pin data, it returns that data instead of executing.
Destination Node: The target node for partial execution, specified with a mode:
The numbered steps below correspond exactly to the comment labels in runPartialWorkflow2():
Diagram: runPartialWorkflow2() ā Step-by-Step with Code Entities
Sources: packages/core/src/execution-engine/workflow-execute.ts197-304
When parameters or connections change, nodes become "dirty" and must be re-executed. The system cleans run data for dirty nodes and all their descendants to ensure consistent execution.
Diagram: Dirty Node Processing ā Code Entity Flow
The cleanRunData() function (called twice during partial execution) removes execution data for the given nodes and all their NodeConnectionTypes.Main descendants in the graph:
| Call Site | nodesToClean | Purpose |
|---|---|---|
| After dirty node detection | dirtyNodes set | Remove stale data so dirty nodes re-execute |
| After start node selection (Step 6) | startNodes set | Remove data for nodes downstream of the start point |
cleanRunData does not mutate runData; it returns a new object. Descendants are found by traversing DirectedGraph.getChildren() for NodeConnectionTypes.Main connections only.
Sources: packages/core/src/execution-engine/partial-execution-utils/clean-run-data.ts packages/core/src/execution-engine/workflow-execute.ts261-271 packages/core/src/execution-engine/__tests__/workflow-execute.test.ts651-757
Sources: packages/workflow/src/interfaces.ts22-36
The findStartNodes() function determines where to begin execution in a partial workflow:
Diagram: Start Node Selection Logic
A node becomes a start node if:
Sources: packages/core/src/execution-engine/partial-execution-utils/find-start-nodes.ts
Pin data allows users to manually specify node output data, overriding normal execution. This is useful for:
Diagram: Pin Data Flow
When pin data exists for a node, the execution engine returns it directly without invoking the node's execute function.
Sources: packages/core/src/execution-engine/workflow-execute.ts1413-1421 packages/core/src/execution-engine/__tests__/workflow-execute.test.ts534-541
The recreateNodeExecutionStack() function rebuilds the node execution queue for partial execution:
Input:
DirectedGraph of relevant nodesIRunData (to skip already-executed nodes)IPinData (to override execution)Output:
nodeExecutionStack: Queue of nodes to execute with their input datawaitingExecution: Data for nodes waiting on multiple inputswaitingExecutionSource: Source tracking for waiting nodesAlgorithm:
waitingExecutionSources: packages/core/src/execution-engine/partial-execution-utils/recreate-node-execution-stack.ts
Destination nodes control the boundary of partial execution:
| Mode | Behavior | Use Case |
|---|---|---|
inclusive | Execute destination node | Testing node output, full partial execution |
exclusive | Stop before destination | Testing inputs to a node, debugging |
The mode affects:
runNodeFilterprocessRunExecutionData()Sources: packages/core/src/execution-engine/workflow-execute.ts126-132
n8n provides multiple layers of error handling to ensure workflow resilience and proper error reporting.
Nodes can be configured to automatically retry on failure. The retry loop runs inside WorkflowExecute.runNode() and checks the AbortSignal between attempts.
The following INode fields control retry behavior:
| Field | Type | Default | Max |
|---|---|---|---|
retryOnFail | boolean | false | ā |
maxTries | number | 3 | 5 |
waitBetweenTries | number (ms) | 1000 | 5000 |
Diagram: Node Retry Loop in WorkflowExecute.runNode()
Sources: packages/core/src/execution-engine/workflow-execute.ts1130-1200
Errors flow through the workflow execution in a structured way, preserving context and allowing for proper handling.
When a node fails, the error is stored in the task data:
Error types include:
NodeOperationError: Errors during node executionNodeApiError: API-related errorsExpressionError: Expression evaluation errorsExecutionCancelledError: Execution cancelled by userSources: packages/workflow/src/interfaces.ts92-99
Nodes can be configured to continue execution even when they fail:
When continueOnFail is enabled:
[[]]This allows workflows to be resilient to individual node failures.
Sources: packages/core/src/execution-engine/workflow-execute.ts1167-1174
Errors are enriched with context as they propagate:
Diagram: Error Propagation Flow
Sources: packages/core/src/execution-engine/workflow-execute.ts1512-1571
n8n uses the standard AbortSignal API to support graceful execution cancellation.
Diagram: Execution Cancellation Architecture
The execution engine checks for cancellation at multiple points:
| Check Point | Location | Action |
|---|---|---|
| Node execution start | WorkflowExecute.processRunExecutionData() | Skip node, mark as canceled |
| Retry wait | sleepWithAbort() | Throw abort error |
| HTTP requests | Request helper functions | Abort request via signal |
| Tool execution | WorkflowToolService.createTool() | Return cancellation message |
| Sub-workflow | executeWorkflow() | Propagate signal to child |
Sources: packages/core/src/execution-engine/workflow-execute.ts1746-1751 packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts117-132
When cancellation occurs:
abortSignal?.abortedexecutionStatus = 'canceled'Example from supply data context:
Sources: packages/core/src/execution-engine/node-execution-context/supply-data-context.ts290-298
Workflows and nodes progress through different execution states:
Diagram: Execution Status State Machine
Sources: packages/workflow/src/execution-status.ts
Nodes can pause execution until a specific time using putExecutionToWait():
When execution resumes:
waitTill time has passedwaitTill from run dataSources: packages/core/src/execution-engine/workflow-execute.ts1957-1980 packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts183-217
Execution metadata tracks additional information about node execution, including errors and sub-executions.
This metadata:
Sources: packages/workflow/src/interfaces.ts24-27 packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts154-162
After execution, metadata from temporary storage is merged into final run data:
Sources: packages/core/src/execution-engine/workflow-execute.ts296-318
Partial execution is also the backbone of AI agent tool invocation. When an agent node needs to call a tool sub-node, it communicates through the EngineRequest / EngineResponse protocol rather than returning data directly.
An agent node's execute() method can return either:
INodeExecutionData[][] ā normal completionEngineRequest ā request to run one or more tool sub-nodesThe engine checks with isEngineRequest() and branches accordingly. Tool nodes execute, and their results are packaged into an EngineResponse which is passed back to the agent on its next execute() call.
Diagram: EngineRequest/EngineResponse Round-Trip in processRunExecutionData
Sources: packages/core/src/execution-engine/requests-response.ts packages/core/src/execution-engine/workflow-execute.ts1004-1072
| Function | File | Role |
|---|---|---|
isEngineRequest(data) | requests-response.ts | Returns true when a node returns an EngineRequest instead of output data |
handleRequest(...) | requests-response.ts | Orchestrates tool node preparation; calls prepareRequestedNodesForExecution |
prepareRequestedNodesForExecution(...) | requests-response.ts | Builds NodeToBeExecuted[] from EngineRequest.actions; populates runData placeholders and the execution stack |
initializeNodeRunData(...) | requests-response.ts | Creates the initial ITaskData entry (with inputOverride) for a tool node invocation |
buildParentOutputData(...) | requests-response.ts | Creates the tool's input INodeExecutionData[][] from the action's input JSON |
makeEngineResponse(...) | requests-response.ts | Assembles the EngineResponse from completed tool run data |
Sources: packages/core/src/execution-engine/requests-response.ts1-250
rewireGraph)When the destination node itself is a tool node (e.g. during runPartialWorkflow2), the graph is rewritten before execution:
NodeHelpers.isTool(destinationNodeType.description, destination.parameters) detects the toolrewireGraph(destination, graph, agentRequest) rewires connections so the tool feeds into a virtual TOOL_EXECUTOR_NODE_NAME nodeTOOL_EXECUTOR_NODE_NAME for the remainder of the executionThis allows the partial execution system to treat a single tool call as a first-class execution target.
Sources: packages/core/src/execution-engine/workflow-execute.ts218-231
When an agent node loops through a tool request, the lifecycle hooks fire once for the agent node even though execute() is called multiple times. The nodeExecuteBefore hook fires on the first call; nodeExecuteAfter fires only when the agent returns final INodeExecutionData[][].
When an n8n process terminates unexpectedly (OOM kill, hardware failure, SIGKILL), in-flight executions remain in running or waiting status in the database with no corresponding process to complete them. The ExecutionRecoveryService handles recovery of these executions on startup.
The ExecutionRepository.markAsCrashed() method performs a bulk UPDATE to set status = 'crashed' and stoppedAt = NOW() for a list of execution IDs:
ExecutionRepository.markAsCrashed(executionIds: string | string[])
ā UPDATE execution SET status='crashed', stoppedAt=<now> WHERE id IN (...)
Updates are batched in groups of 900 (MAX_UPDATE_BATCH_SIZE) to avoid database IN-clause limits.
Sources: packages/@n8n/db/src/repositories/execution.repository.ts349-366
| Status | Meaning |
|---|---|
crashed | Process terminated mid-execution; partial data may exist |
error | Node threw an error; execution completed with failure |
canceled | User or system explicitly stopped the execution |
success | All nodes completed without error |
Crashed executions are excluded from getWaitingExecutions() (which uses Not('crashed')) to prevent the WaitTracker from attempting to resume them.
Sources: packages/@n8n/db/src/repositories/execution.repository.ts589-611
ExecutionService.retry() can be called on a crashed execution to resume from the point of failure:
IRunExecutionData from the databaseresultData for lastNodeExecutedrunData[lastNodeExecuted] (if it was an error)WorkflowRunner.run() with mode = 'retry'nodeExecutionStack in executionData drives which nodes re-executeIf loadWorkflow: true is passed, the current saved workflow definition is used instead of the one frozen at the time of the original execution, and node references in the stack are updated to match.
Sources: packages/cli/src/executions/execution.service.ts183-328
The complete execution data structure that tracks all execution state. Created with createRunExecutionData() from n8n-workflow:
IRunExecutionData
āāā startData?
ā āāā destinationNode?: IDestinationNode { nodeName, mode: 'inclusive'|'exclusive' }
ā āāā originalDestinationNode?: IDestinationNode
ā āāā runNodeFilter?: string[] nodes allowed to execute
āāā resultData
ā āāā runData: IRunData { [nodeName]: ITaskData[] }
ā āāā pinData?: IPinData { [nodeName]: INodeExecutionData[] }
ā āāā lastNodeExecuted?: string
ā āāā error?: ExecutionBaseError
āāā executionData?
ā āāā nodeExecutionStack: IExecuteData[] nodes ready to execute
ā āāā waitingExecution: IWaitingForExecution
ā āāā waitingExecutionSource?: IWaitingForExecutionSource
ā āāā metadata?: { [nodeName]: ITaskMetadata[] }
āāā waitTill?: Date
The startData.runNodeFilter array is critical for partial execution: only nodes whose names appear in this list are allowed to execute. filterDisabledNodes() ensures disabled nodes are not included.
Sources: packages/core/src/execution-engine/workflow-execute.ts283-298
Represents a single node ready for execution on the stack:
| Field | Type | Description |
|---|---|---|
node | INode | The node definition to execute |
data | ITaskDataConnections | Input data keyed by connection type (main, ai_tool, etc.) |
source | ITaskDataConnectionsSource | null | Tracks which upstream nodes and run indices provided the input |
runIndex | number? | Distinguishes multiple executions of the same node (e.g., SplitInBatches) |
metadata | ITaskMetadata? | Additional metadata including subNodeExecutionData for agent tool loops |
Sources: packages/core/src/execution-engine/workflow-execute.ts530-545
The test suite extensively covers partial execution scenarios:
| Test Category | File | Focus |
|---|---|---|
| Dirty node handling | workflow-execute.test.ts:651-757 | cleanRunData removes dirty + descendants |
| Disabled nodes | workflow-execute.test.ts:759-810 | filterDisabledNodes keeps disabled out of runNodeFilter but not graph |
| EngineRequest / tool loops | workflow-execute-process-process-run-execution-data.test.ts:122-462 | Agent requests tool, resumes with response |
cleanRunData unit tests | clean-run-data.test.ts | cleanRunData behavior for various graph shapes |
| Hook ordering | workflow-execute.test.ts:220-465 | Hooks fire correctly for destination and sibling nodes |
| Waiting state | workflow-execute-process-process-run-execution-data.test.ts:190-224 | waitTill handling and resume |
Sources: packages/core/src/execution-engine/__tests__/workflow-execute.test.ts packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts packages/core/src/execution-engine/partial-execution-utils/__tests__/clean-run-data.test.ts
The runPartialWorkflow2 tests use DirectedGraph builder syntax to create workflows inline. The toITaskData helper wraps raw JSON into ITaskData format for runData fixtures:
trigger ā node1(dirty) ā node2(destination)
After calling runPartialWorkflow2 with dirtyNodeNames = ['node1']:
runData retains trigger (unchanged, not a child of dirty node)runData drops node1 (dirty), node2 (child of dirty node)Sources: packages/core/src/execution-engine/__tests__/workflow-execute.test.ts651-692
Partial execution and error handling in n8n work together to provide:
The system balances performance (reusing existing data) with correctness (detecting and re-executing changes) while providing robust error handling and recovery mechanisms.
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.