vercel/ai · error · ToolCallNotFoundForApprovalError
Tool call "${toolCallId}" not found for approval request "${
Error message
Tool call "${toolCallId}" not found for approval request "${approvalId}". What it means
Each tool-approval-request in history must correspond to a tool-call part with the same toolCallId. If collectToolApprovals finds the approval request but cannot find its associated tool call, ToolCallNotFoundForApprovalError is thrown. This ensures approvals always act on an actual recorded tool call.
Source
Thrown at packages/ai/src/generate-text/collect-tool-approvals.ts:113
if (approvalRequest == null) {
throw new InvalidToolApprovalError({
approvalId: approvalResponse.approvalId,
});
}
const existingToolResult = toolResults[approvalRequest.toolCallId];
if (
existingToolResult != null &&
(approvalResponse.approved ||
existingToolResult.output.type !== 'execution-denied')
) {
continue;
}
const toolCall = toolCallsByToolCallId[approvalRequest.toolCallId];
if (toolCall == null) {
throw new ToolCallNotFoundForApprovalError({
toolCallId: approvalRequest.toolCallId,
approvalId: approvalRequest.approvalId,
});
}
const approval: CollectedToolApprovals<TOOLS> = {
approvalRequest,
approvalResponse,
toolCall,
...(existingToolResult != null ? { existingToolResult } : {}),
};
if (approvalResponse.approved) {
approvedToolApprovals.push(approval);
} else {
deniedToolApprovals.push(approval);
}
}View on GitHub (pinned to 69428b1f8b)
Solutions
- Keep tool-call parts together with their tool-approval-request parts in persisted history
- Rebuild the conversation so the tool call preceding the approval request is present
- If history is unrecoverable, restart the tool flow from the original user turn
- Validate each approval request has a matching toolCallId before resuming
Example fix
// before messages: parts.filter(p => p.type !== 'tool-call') // after messages: parts // retain tool-call parts referenced by approval requests
Defensive patterns
Strategy: validation
Validate before calling
const toolCallIds = new Set(messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-call').map(p => p.toolCallId));
const dangling = messages.flatMap(m => m.parts ?? [])
.filter(p => p.type === 'tool-approval-request' && !toolCallIds.has(p.toolCallId));
if (dangling.length) throw new Error('Approval requests without matching tool calls'); Type guard
function hasToolCallsForApprovals(messages: Message[]): boolean {
const calls = new Set(messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-call').map(p => p.toolCallId));
return messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-approval-request').every(p => calls.has(p.toolCallId));
} Try / catch
try {
return await generateText({ messages, experimental_toolApprovals });
} catch (e) {
if (ToolCallNotFoundForApprovalError.isInstance(e)) {
console.error('missing tool call', e.toolCallId, 'for approval', e.approvalId);
}
throw e;
} Prevention
- Store tool-call and tool-approval-request parts together
- Do not prune tool-call parts from persisted history
- Keep all parts of one assistant step atomic when saving
When it happens
Trigger: Message history contains a tool-approval-request whose toolCallId has no matching tool-call part; history was pruned to remove tool calls but kept approval requests; approval request and tool call came from divergent conversation copies.
Common situations: Selective persistence of message parts (saving approvals but not raw tool calls); migrating between SDK versions where part shapes changed; manually assembling resume messages from partial logs.
Related errors
- Tool approval response references unknown approvalId: "${app
- Tool call ${part.toolCallId} not found.
- Tool call "${toolCallId}" not found for approval request "${
- Tool approval signature verification failed for approval "${
- No tool invocation found for approval ID "${approvalId}".
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/3a12edd44a7d1b74.
Report an issue: GitHub.