n8n-io/n8n · critical · Error
Checkpoint for runId ${this.runId} has pending tool calls —
Error message
Checkpoint for runId ${this.runId} has pending tool calls — crashResume only accepts step checkpoints What it means
Critical error thrown when the workflow graph is empty — zero nodes. An empty workflow cannot be executed or serialized meaningfully, so validation fails fast.
Source
Thrown at packages/@n8n/agents/src/runtime/loop/agent-runtime.ts:524
* verify before retrying") and undrained steering corrections recovered
* from its durable event log. Tool calls are never re-executed mechanically.
*/
async crashResume(
options: { runId: string; contextNotes?: string[] } & ExecutionOptions,
): Promise<StreamResult> {
this.runId = options.runId;
const state = await this.runState.loadForCrashResume(this.runId);
if (!state) throw new Error(`No checkpoint found for runId: ${this.runId}`);
if (state.status !== 'running') {
throw new Error(
`Checkpoint for runId ${this.runId} has status '${state.status}' — crashResume only accepts step checkpoints; use resume() for suspended runs`,
);
}
// A claimed HITL resume also persists as 'running' but still carries its
// pending tool calls; re-driving it would skip settling them. Step
// checkpoints are always written with empty pendingToolCalls.
if (Object.keys(state.pendingToolCalls).length > 0) {
throw new Error(
`Checkpoint for runId ${this.runId} has pending tool calls — crashResume only accepts step checkpoints`,
);
}
const list = AgentMessageList.deserialize(state.messageList);
this.context.hydrateDeferredToolsFromList(list);
await hydrateFileParts(list.messages(), this.config.fileStore, {
threadId: state.persistence?.threadId,
});
let abortScope: AgentAbortScope | undefined;
try {
const { runId: _rid, contextNotes, ...callerExecOptions } = options;
const persisted = state.executionOptions ?? {};
const persistedMaxIterations = persisted.maxIterations;
const callerMaxIterations = callerExecOptions.maxIterations;
if (
callerMaxIterations !== undefined &&View on GitHub (pinned to 5ac6606e81)
Solutions
- Add at least one node (typically a trigger) before validating.
- Check the upstream generation step that produced an empty node list — it usually signals an earlier failure.
- Guard your generation code to refuse to emit a workflow with no nodes.
Example fix
// before
const wf = new WorkflowBuilder();
// ...nothing added...
wf.validate();
// after
const wf = new WorkflowBuilder();
const t = wf.add(manualTrigger({ name: 'Start' }));
t.to(set({ name: 'Init', assignments: { assignments: [] } }));
wf.validate(); Defensive patterns
Strategy: validation
Validate before calling
function assertNonEmpty(nodes: Array<unknown>): void {
if (nodes.length === 0) throw new Error('Refusing to build a workflow with no nodes.');
}
// before validating:
assertNonEmpty([...builder.nodes]); Prevention
- Guard generation pipelines to never return an empty node list.
- Treat an empty graph from an upstream step as a hard error, not a fallback.
- Add at least one trigger before calling validate.
When it happens
Trigger: ctx.nodes.size === 0 at validateWorkflow time. The validator runs at high priority early specifically to catch this.
Common situations: A builder script that constructed a WorkflowBuilder but never added nodes before validating; an AI agent emitted only connections/metadata; a programmatic generation pipeline returned an empty graph on error.
Related errors
- Cannot decrease maxIterations when resuming a run. Expected
- No checkpoint found for runId: ${this.runId}
- Checkpoint for runId ${this.runId} has status '${state.statu
- Reflector output must be valid JSON
- Model ID is required
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/eb58562a9ff4257b.
Report an issue: GitHub.