{"record":{"id":"3a12edd44a7d1b74","repo":"vercel/ai","slug":"tool-call-toolcallid-not-found-for-approval-r","errorCode":null,"errorMessage":"Tool call \"${toolCallId}\" not found for approval request \"${approvalId}\".","messagePattern":"Tool call \"(.+?)\" not found for approval request \"(.+?)\"\\.","errorType":"exception","errorClass":"ToolCallNotFoundForApprovalError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/generate-text/collect-tool-approvals.ts","lineNumber":113,"sourceCode":"\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,\n      });\n    }\n\n    const approval: CollectedToolApprovals<TOOLS> = {\n      approvalRequest,\n      approvalResponse,\n      toolCall,\n      ...(existingToolResult != null ? { existingToolResult } : {}),\n    };\n\n    if (approvalResponse.approved) {\n      approvedToolApprovals.push(approval);\n    } else {\n      deniedToolApprovals.push(approval);\n    }\n  }","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/ai/src/generate-text/collect-tool-approvals.ts#L95-L131","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nmessages: parts.filter(p => p.type !== 'tool-call')\n// after\nmessages: parts // retain tool-call parts referenced by approval requests","handlingStrategy":"validation","validationCode":"const toolCallIds = new Set(messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-call').map(p => p.toolCallId));\nconst dangling = messages.flatMap(m => m.parts ?? [])\n  .filter(p => p.type === 'tool-approval-request' && !toolCallIds.has(p.toolCallId));\nif (dangling.length) throw new Error('Approval requests without matching tool calls');","typeGuard":"function hasToolCallsForApprovals(messages: Message[]): boolean {\n  const calls = new Set(messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-call').map(p => p.toolCallId));\n  return messages.flatMap(m => m.parts ?? []).filter(p => p.type === 'tool-approval-request').every(p => calls.has(p.toolCallId));\n}","tryCatchPattern":"try {\n  return await generateText({ messages, experimental_toolApprovals });\n} catch (e) {\n  if (ToolCallNotFoundForApprovalError.isInstance(e)) {\n    console.error('missing tool call', e.toolCallId, 'for approval', e.approvalId);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["tool-approval","message-history","tool-call"],"backgroundTag":"stale-approval-reference","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}