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

  1. Add at least one node (typically a trigger) before validating.
  2. Check the upstream generation step that produced an empty node list — it usually signals an earlier failure.
  3. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/eb58562a9ff4257b. Report an issue: GitHub.