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

  1. Persist and resend the complete message history including the original tool-approval-request parts
  2. Ensure approval responses are generated by the SDK (from the resume flow) rather than hand-crafted
  3. Regenerate the approval flow by re-running the tool call if history cannot be recovered
  4. 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

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


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/c66aef91da4d4340. Report an issue: GitHub.