vercel/ai · error · InvalidToolApprovalError
Tool approval response references unknown approvalId: "${app
Error message
Tool approval response references unknown approvalId: "${approvalId}". No matching tool-approval-request found in message history. What it means
When resuming a conversation that contains tool-approval responses, collectToolApprovals matches each response's approvalId against tool-approval-request parts found in the message history. If no matching request exists, InvalidToolApprovalError is thrown. This protects against replayed, fabricated, or out-of-order approval data.
Source
Thrown at packages/ai/src/generate-text/collect-tool-approvals.ts:97
const toolResults: Record<string, ToolResultPart> = Object.create(null);
for (const part of lastMessage.content) {
if (part.type === 'tool-result') {
toolResults[part.toolCallId] = part;
}
}
const approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>> = [];
const deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>> = [];
const approvalResponses = lastMessage.content.filter(
part => part.type === 'tool-approval-response',
);
for (const approvalResponse of approvalResponses) {
const approvalRequest =
toolApprovalRequestsByApprovalId[approvalResponse.approvalId];
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,View on GitHub (pinned to 69428b1f8b)
Solutions
- Persist and resend the complete message history including the original tool-approval-request parts
- Ensure approval responses are generated by the SDK (from the resume flow) rather than hand-crafted
- Regenerate the approval flow by re-running the tool call if history cannot be recovered
- Verify approvalIds in responses match those in messages (see validationCode)
Example fix
// before messages: history.filter(m => m.role !== 'tool') // dropped approval requests // after messages: history // keep tool-approval-request parts intact
Defensive patterns
Strategy: validation
Validate before calling
const requestIds = new Set(
messages.flatMap(m => m.parts ?? [])
.filter(p => p.type === 'tool-approval-request')
.map(p => p.approvalId)
);
const unresolved = approvalResponses.filter(r => !requestIds.has(r.approvalId));
if (unresolved.length) throw new Error('Unknown approvalIds: ' + unresolved.map(u => u.approvalId)); Type guard
function approvalsMatchHistory(responses: { approvalId: string }[], messages: Message[]): boolean {
const ids = new Set(messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-approval-request').map(p => p.approvalId));
return responses.every(r => ids.has(r.approvalId));
} Try / catch
try {
return await generateText({ messages, experimental_toolApprovals });
} catch (e) {
if (InvalidToolApprovalError.isInstance(e)) {
console.error('unknown approvalId:', e.approvalId);
}
throw e;
} Prevention
- Persist the full message history including tool-approval-request parts
- Never hand-craft approval responses; use SDK-generated ones
- Resume the same conversation that issued the approval request
When it happens
Trigger: Calling generateText/streamText with `experimental_toolApprovals`/approval responses whose approvalId does not appear in the supplied messages; trimming or reconstructing message history and dropping the original tool-approval-request part; duplicating or hand-editing approval responses.
Common situations: Persisting only the approval response but not the request part in a database; truncating history to the last N messages; manual message construction missing the tool-approval-request; resuming a different/older conversation than the one that issued the request.
Related errors
- Tool call "${toolCallId}" not found for approval request "${
- Tool approval signature verification failed for approval "${
- maxEmbeddingsPerCall must be greater than 0
- maxInputBytesPerCall must be greater than 0
- No image generated.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/c66aef91da4d4340.
Report an issue: GitHub.