vercel/ai · error · UIMessageStreamError
No tool invocation found for tool call ID "${toolCallId}".
Error message
No tool invocation found for tool call ID "${toolCallId}". What it means
In process-ui-message-stream.ts, when a tool-result or related chunk arrives, getToolInvocation looks up the existing tool invocation by toolCallId in the message's toolInvocations. If none matches, it throws a UIMessageStreamError. The stream references a tool call that was never started (no tool-input-start / tool call part created first).
Source
Thrown at packages/ai/src/ui/process-ui-message-stream.ts:149
let toolInvocation = toolInvocations.find(
invocation => invocation.toolCallId === toolCallId,
);
if (toolInvocation == null) {
const parts = state.message.parts;
for (let i = parts.length - 1; i >= 0; i--) {
const part = parts[i];
if (isToolUIPart(part) && part.toolCallId === toolCallId) {
toolInvocation = part;
break;
}
}
}
if (toolInvocation == null) {
throw new UIMessageStreamError({
chunkType: 'tool-invocation',
chunkId: toolCallId,
message: `No tool invocation found for tool call ID "${toolCallId}".`,
});
}
return toolInvocation;
}
function getToolInvocationByApprovalId(approvalId: string) {
const toolInvocations = state.message.parts.filter(isToolUIPart);
const toolInvocation = toolInvocations.find(
invocation => invocation.approval?.id === approvalId,
);
if (toolInvocation == null) {
throw new UIMessageStreamError({View on GitHub (pinned to 69428b1f8b)
Solutions
- Ensure the server always sends a 'tool-input-start' (tool call announcement) chunk before any tool-input-delta or tool-result chunks for that toolCallId.
- Verify toolCallIds are consistent between the stream chunks and any persisted message history being appended to.
- When resuming, replay or include the earlier tool-start chunks so state contains the invocation.
- Catch UIMessageStreamError in the stream processing and skip/handle orphan tool-result chunks.
Example fix
// before (server custom stream)
writer.write({ type: 'tool-result', toolCallId: 'call_1', result });
// after
writer.write({ type: 'tool-input-start', toolCallId: 'call_1', toolName: 'getWeather' });
writer.write({ type: 'tool-input-available', toolCallId: 'call_1', input });
writer.write({ type: 'tool-result', toolCallId: 'call_1', result }); Defensive patterns
Strategy: validation
Validate before calling
const exists = message.parts.some(
p => p.type === 'tool' && p.toolCallId === toolCallId,
);
if (!exists) throw new Error(`Cannot emit result: unknown toolCallId ${toolCallId}`); Type guard
function hasToolInvocation(msg: UIMessage, toolCallId: string): boolean {
return msg.parts.some(
(p): p is Extract<UIMessage['parts'][number], { type: 'tool' }> =>
p.type === 'tool' && p.toolCallId === toolCallId,
);
} Try / catch
try {
await processUIMessageStream(...);
} catch (e) {
if (UIMessageStreamError.isInstance?.(e) || (e instanceof Error && /No tool invocation found for tool call ID/.test(e.message))) {
console.warn('Orphan tool result chunk skipped', e.message);
} else throw e;
} Prevention
- Always emit tool-input-start before tool results in custom stream writers.
- Reuse the writer helpers from the SDK instead of hand-writing raw chunks.
- Persist and replay the full chunk sequence including start chunks on resume.
- Generate toolCallIds in one place to avoid mismatches.
When it happens
Trigger: Consuming a UI message stream where a 'tool-input-available'/'tool-result' chunk carries a toolCallId that has no matching tool invocation in the message state — e.g. manually constructed streams, stream resumed mid-way dropping earlier chunks, or server sending results for calls it never announced.
Common situations: Custom hand-rolled stream writers on the server emitting tool results without tool-input-start; resuming a stream where the original tool-start chunks were already consumed; stream chunk loss in persistence/replay; mismatched toolCallIds after server-side regeneration.
Related errors
- Received tool-input-delta for missing tool call with ID "${c
- No tool invocation found for approval ID "${approvalId}".
- Received text-delta for missing text part with ID "${chunk.i
- Received text-end for missing text part with ID "${chunk.id}
- Received reasoning-delta for missing reasoning part with ID
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/bceea9b54e454f83.
Report an issue: GitHub.