{"record":{"id":"c66aef91da4d4340","repo":"vercel/ai","slug":"tool-approval-response-references-unknown-approval","errorCode":null,"errorMessage":"Tool approval response references unknown approvalId: \"${approvalId}\". No matching tool-approval-request found in message history.","messagePattern":"Tool approval response references unknown approvalId: \"(.+?)\"\\. No matching tool-approval-request found in message history\\.","errorType":"exception","errorClass":"InvalidToolApprovalError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/generate-text/collect-tool-approvals.ts","lineNumber":97,"sourceCode":"  const toolResults: Record<string, ToolResultPart> = Object.create(null);\n  for (const part of lastMessage.content) {\n    if (part.type === 'tool-result') {\n      toolResults[part.toolCallId] = part;\n    }\n  }\n\n  const approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>> = [];\n  const deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>> = [];\n\n  const approvalResponses = lastMessage.content.filter(\n    part => part.type === 'tool-approval-response',\n  );\n  for (const approvalResponse of approvalResponses) {\n    const approvalRequest =\n      toolApprovalRequestsByApprovalId[approvalResponse.approvalId];\n\n    if (approvalRequest == null) {\n      throw new InvalidToolApprovalError({\n        approvalId: approvalResponse.approvalId,\n      });\n    }\n\n    const existingToolResult = toolResults[approvalRequest.toolCallId];\n    if (\n      existingToolResult != null &&\n      (approvalResponse.approved ||\n        existingToolResult.output.type !== 'execution-denied')\n    ) {\n      continue;\n    }\n\n    const toolCall = toolCallsByToolCallId[approvalRequest.toolCallId];\n    if (toolCall == null) {\n      throw new ToolCallNotFoundForApprovalError({\n        toolCallId: approvalRequest.toolCallId,\n        approvalId: approvalRequest.approvalId,","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/ai/src/generate-text/collect-tool-approvals.ts#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nmessages: history.filter(m => m.role !== 'tool') // dropped approval requests\n// after\nmessages: history // keep tool-approval-request parts intact","handlingStrategy":"validation","validationCode":"const requestIds = new Set(\n  messages.flatMap(m => m.parts ?? [])\n    .filter(p => p.type === 'tool-approval-request')\n    .map(p => p.approvalId)\n);\nconst unresolved = approvalResponses.filter(r => !requestIds.has(r.approvalId));\nif (unresolved.length) throw new Error('Unknown approvalIds: ' + unresolved.map(u => u.approvalId));","typeGuard":"function approvalsMatchHistory(responses: { approvalId: string }[], messages: Message[]): boolean {\n  const ids = new Set(messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-approval-request').map(p => p.approvalId));\n  return responses.every(r => ids.has(r.approvalId));\n}","tryCatchPattern":"try {\n  return await generateText({ messages, experimental_toolApprovals });\n} catch (e) {\n  if (InvalidToolApprovalError.isInstance(e)) {\n    console.error('unknown approvalId:', e.approvalId);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["tool-approval","message-history","validation"],"backgroundTag":"stale-approval-reference","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}