n8n-io/n8n · error · Error
No pending tool call found for toolCallId: ${resumedId}
Error message
No pending tool call found for toolCallId: ${resumedId} What it means
Thrown during tool-call resume when pendingResume.pendingToolCalls does not contain an entry for the resumeToolCallId. The resume path expects to find the previously-suspended tool call in the pending map so it can re-execute it with the resume data. A missing entry means the checkpoint referenced a tool call that was never suspended or has already been completed/cleaned up.
Source
Thrown at packages/@n8n/agents/src/runtime/tools/tool-call-executor.ts:501
* Returns a `ToolCallBatchResult` — the caller handles persistence.
*/
async iteratePendingToolCallsConcurrent(
ctx: ToolBatchContext & { pendingResume: PendingResume },
): Promise<ToolCallBatchResult> {
const {
pendingResume,
toolMap,
list,
runId,
persistence,
telemetry: resolvedTelemetry,
executionCounter,
abortSignal,
} = ctx;
const resumedId = pendingResume.resumeToolCallId;
const resumedEntry = pendingResume.pendingToolCalls[resumedId];
if (!resumedEntry) {
throw new Error(`No pending tool call found for toolCallId: ${resumedId}`);
}
const resumedToolName = resumedEntry.toolName;
const results: ToolCallSuccess[] = [];
const suspensions: ToolCallSuspension[] = [];
const errors: ToolCallError[] = [];
const pending: Record<string, PendingToolCall> = {};
// 1. Execute the resumed tool
const processResult = await this.processToolCall(
this.pendingToolCallParams(resumedEntry, ctx, pendingResume.resumeData),
);
if (processResult.outcome === 'suspended') {
pending[resumedId] = {
suspended: true,
toolCallId: resumedEntry.toolCallId,
toolName: resumedToolName,View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure each suspended execution is resumed exactly once — use idempotency keys or a lock on the execution ID.
- Verify the checkpoint store correctly persists and restores the pendingToolCalls map.
- If the resume token is stale, start a fresh execution instead of resuming.
- Add logging to capture the available pendingToolCallIds vs the requested resumeToolCallId at resume time.
Defensive patterns
Strategy: validation
Validate before calling
function hasPendingToolCall(pendingToolCalls: Record<string, unknown>, resumeToolCallId: string): boolean {
return resumeToolCallId in pendingToolCalls;
}
if (!hasPendingToolCall(ctx.pendingToolCalls, resumeData.resumeToolCallId)) {
// do not attempt resume; start a new execution
} Try / catch
try {
await executor.resume(pendingResume);
} catch (e) {
if (e instanceof Error && e.message.startsWith('No pending tool call found')) {
// resume token is stale; start a fresh execution
} else throw e;
} Prevention
- Use idempotency guards or locks to ensure each suspended execution resumes exactly once.
- Verify the checkpoint store correctly persists and restores the pendingToolCalls map.
- Log available vs requested toolCallIds to diagnose mismatches early.
When it happens
Trigger: Resuming an execution with a resumeToolCallId that does not match any key in pendingToolCalls. This happens if the checkpoint store returns a stale or already-consumed resume token, if the tool call was completed in a separate resume attempt, or if the pending map was reconstructed incorrectly from persistence.
Common situations: Double-resume: the same suspended execution is resumed twice (the first resume consumed the pending entry). Checkpoint store does not enforce single-consumer semantics. Race condition where two resume requests hit the same execution. Checkpoint data corruption or schema migration that lost the pending tool call map.
Related errors
- Delegated child checkpoint metadata is missing or invalid
- Delegated child checkpoint does not match the selected sub-a
- Run ${runId} is not suspended. Cannot resume.
- toolCallConcurrency must be a positive integer or Infinity
- Agent "${this.name}" has tools requiring approval or suspend
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/b154205c6f15bf89.
Report an issue: GitHub.