mastra-ai/mastra · error
tool_result must be preceded by a tool_call with the same to
Error message
tool_result must be preceded by a tool_call with the same toolCallId
What it means
This error is thrown when processing a stream part carrying a tool result whose toolCallId does not match any pending tool invocation in the message being built. The library maintains an ordered list of toolInvocations; a 'tool-result' must correspond to a 'tool-call' that was previously emitted with the same toolCallId, otherwise the message state machine is inconsistent.
Source
Thrown at client-sdks/client-js/src/resources/agent.ts:1585
updateToolInvocationPart(value.toolCallId, invocation, (value as MaybeProviderMetadata).providerMetadata);
execUpdate();
}
}
},
onToolResultPart(value) {
const toolInvocations = message.toolInvocations;
if (toolInvocations == null) {
throw new Error('tool_result must be preceded by a tool_call');
}
// find if there is any tool invocation with the same toolCallId
// and replace it with the result
const toolInvocationIndex = toolInvocations.findIndex(invocation => invocation.toolCallId === value.toolCallId);
if (toolInvocationIndex === -1) {
throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
}
const invocation = {
...toolInvocations[toolInvocationIndex],
state: 'result' as const,
...value,
} as const;
toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
updateToolInvocationPart(
value.toolCallId,
invocation as ToolInvocation,
(value as MaybeProviderMetadata).providerMetadata,
);
execUpdate();
},View on GitHub (pinned to 75dd419e61)
Solutions
- Ensure the full stream is consumed from the start — do not skip or drop tool-call chunks before the matching tool-result.
- Check any stream middleware/processors for logic that removes or reorders tool-call parts.
- Clear stale conversation memory so the client rebuilds messages from a consistent stream.
- Verify server and client-js versions match and use the same UI-message stream protocol.
Example fix
// before: filtering stream parts
stream.processDataStream({ onChunk: c => { if (c.type !== 'tool-call') handle(c); } });
// after: handle every part so toolInvocations is populated first
stream.processDataStream({ onChunk: c => handle(c) }); Defensive patterns
Strategy: validation
Validate before calling
function canApplyToolResult(message, toolCallId) {
return Array.isArray(message.toolInvocations) &&
message.toolInvocations.some(inv => inv.toolCallId === toolCallId);
}
// before merging a tool-result part: if (!canApplyToolResult(message, value.toolCallId)) skip or buffer it; Type guard
function hasMatchingToolInvocation(message: UIMessage, toolCallId: string): boolean {
return Array.isArray(message.toolInvocations) &&
message.toolInvocations.some(i => i.toolCallId === toolCallId);
} Try / catch
try {
await stream.processDataStream({ onChunk, onFinish });
} catch (err) {
if (err instanceof Error && err.message.includes('tool_result must be preceded')) {
console.error('Inconsistent tool stream: result without matching call', err.message);
} else throw err;
} Prevention
- Always consume streams from the first chunk; never skip tool-call parts.
- Keep middleware/processors idempotent and non-destructive to tool-call chunks.
- Pin matching versions of @mastra/core and @mastra/client-js.
- Never rewrite toolCallId between the call and result parts.
When it happens
Trigger: Consuming a stream (agent.stream/.streamVNext UI message stream) where a tool-result part arrives while message.toolInvocations is missing an entry with a matching toolCallId — e.g. a result part for a call the client never saw, a duplicated/replayed result, or a stream where earlier tool-call chunks were dropped or filtered.
Common situations: Custom middleware or processors that strip tool-call parts; resuming/remembering a conversation where stored messages lost their tool-call entries; feeding a server stream through a transform that reorders or drops chunks; version mismatches between server and @mastra/client-js stream formats.
Related errors
- UI Messages require a data property when using data- prefixe
- UI Messages require a data property when using data- prefixe
- UI Messages require a data property when using data- prefixe
- Failed to stream background tasks: ${response.statusText}
- Response body is null
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/4c2ffc467e187782.
Report an issue: GitHub.