{"record":{"id":"e239b553bb9cd73a","repo":"vercel/ai","slug":"no-tool-invocation-found-for-approval-id-approv","errorCode":null,"errorMessage":"No tool invocation found for approval ID \"${approvalId}\".","messagePattern":"No tool invocation found for approval ID \"(.+?)\"\\.","errorType":"exception","errorClass":"UIMessageStreamError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/ui/process-ui-message-stream.ts","lineNumber":167,"sourceCode":"              throw new UIMessageStreamError({\n                chunkType: 'tool-invocation',\n                chunkId: toolCallId,\n                message: `No tool invocation found for tool call ID \"${toolCallId}\".`,\n              });\n            }\n\n            return toolInvocation;\n          }\n\n          function getToolInvocationByApprovalId(approvalId: string) {\n            const toolInvocations = state.message.parts.filter(isToolUIPart);\n\n            const toolInvocation = toolInvocations.find(\n              invocation => invocation.approval?.id === approvalId,\n            );\n\n            if (toolInvocation == null) {\n              throw new UIMessageStreamError({\n                chunkType: 'tool-approval-response',\n                chunkId: approvalId,\n                message: `No tool invocation found for approval ID \"${approvalId}\".`,\n              });\n            }\n\n            return toolInvocation;\n          }\n\n          function updateToolPart(\n            options: {\n              toolName: keyof InferUIMessageTools<UI_MESSAGE> & string;\n              toolCallId: string;\n              providerExecuted?: boolean;\n              title?: string;\n              toolMetadata?: JSONObject;\n            } & (\n              | {","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/ai/src/ui/process-ui-message-stream.ts#L149-L185","documentation":"In process-ui-message-stream.ts, a 'tool-approval-response' chunk carries an approvalId; getToolInvocationByApprovalId searches the message's tool invocations for one whose `approval.id` matches. If no tool invocation has that approval ID, a UIMessageStreamError is thrown. The approval response references an approval request that does not exist in current message state.","triggerScenarios":"Sending/processing a tool-approval-response chunk whose approvalId has no matching tool invocation (no prior tool-approval-request chunk, or the invocation was in a different message), e.g. when addToolApprovalResponse is called with a stale or wrong approval ID.","commonSituations":"Approving after the message list was trimmed/reset so the approval request no longer exists; copy-pasted or hardcoded approval IDs; multiple approval requests where the user's UI passed the wrong one; reconnect/replay dropping the approval-request chunk.","solutions":["Ensure the tool invocation with `approval.id === approvalId` exists in the target message before emitting the approval response (keep messages containing the approval request).","Use the approval object returned by the tool-approval-request chunk directly instead of manually constructed IDs.","Check that approval responses target the correct message and are not split across conversations.","Catch UIMessageStreamError for tool-approval-response chunks and surface a user-friendly 'approval no longer valid' state."],"exampleFix":"// before\ntransport.sendMessages({ chatId, messages, trigger: 'tool-approval-response', ... }) // stale approvalId\n// after\nconst approval = message.parts.find(p => p.type === 'tool-approval-request')?.approval;\n// use approval.id from the live message part to build the response","handlingStrategy":"validation","validationCode":"const target = messages\n  .flatMap(m => m.parts)\n  .find(p => p.type === 'tool' && p.approval?.id === approvalId);\nif (!target) throw new Error(`No pending approval with id ${approvalId}`);","typeGuard":"function hasApproval(msg: UIMessage, approvalId: string): boolean {\n  return msg.parts.some(p => p.type === 'tool' && p.approval?.id === approvalId);\n}","tryCatchPattern":"try {\n  await sendToolApprovalResponse({ approvalId, approved });\n} catch (e) {\n  if (e instanceof Error && /No tool invocation found for approval ID/.test(e.message)) {\n    setApprovalState('expired'); // show 'approval no longer valid' UI\n  } else throw e;\n}","preventionTips":["Build approval responses from the approval object in the live message part, never hardcoded IDs.","Keep messages containing pending approval requests in state until resolved.","Disable approve/reject UI once the message list resets or the approval is consumed.","Clear stale approval IDs after reconnects or message trimming."],"tags":["streaming","tool-approval","human-in-the-loop","state-mismatch"],"backgroundTag":"missing-stream-chunk","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}