mastra-ai/mastra · error
tool_result must be preceded by a tool_call
Error message
tool_result must be preceded by a tool_call
What it means
In agent.ts, onToolResultPart throws 'tool_result must be preceded by a tool_call' when a stream chunk carries a tool result but message.toolInvocations is null, meaning no earlier tool_call chunk created an invocation entry. The stream protocol requires each tool_result to correspond to a tracked tool invocation so the result can replace the pending call by toolCallId.
Source
Thrown at client-sdks/client-js/src/resources/agent.ts:1577
step,
...value,
result,
} as const;
// store the result in the tool invocation
message.toolInvocations![message.toolInvocations!.length - 1] = invocation;
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;
View on GitHub (pinned to 75dd419e61)
Solutions
- Ensure you consume the stream from the beginning (or from a checkpoint that includes the tool_call chunk).
- Verify server/middleware isn't dropping or reordering tool_call parts.
- Initialize message.toolInvocations to [] on the message object before processing chunks if you process partial streams defensively.
- Check SDK/server version compatibility for the stream protocol's tool part shapes.
- Guard with a null check and skip/log orphan tool results instead of throwing, if out-of-order data is acceptable in your app.
Example fix
// before
onToolResultPart(value) {
const toolInvocations = message.toolInvocations;
if (toolInvocations == null) throw new Error('tool_result must be preceded by a tool_call');
// after (app-side guard when processing partial streams)
if (!message.toolInvocations) { console.warn('Skipping orphan tool_result', value.toolCallId); return; }
const idx = message.toolInvocations.findIndex(i => i.toolCallId === value.toolCallId); Defensive patterns
Strategy: validation
Validate before calling
// before processing a partial/historical stream, ensure invocation state exists
if (!message.toolInvocations) message.toolInvocations = [];
const knownCallIds = new Set(message.toolInvocations.map(i => i.toolCallId));
if ('toolCallId' in incomingResult && !knownCallIds.has(incomingResult.toolCallId)) {
console.warn('tool_result without matching tool_call — stream may be partial/out of order');
} Type guard
function hasToolInvocations(m: unknown): m is { toolInvocations: Array<{ toolCallId: string }> } {
return !!m && typeof m === 'object' && Array.isArray((m as any).toolInvocations);
} Try / catch
try {
await consumeAgentStream(stream);
} catch (err) {
if ((err as Error).message === 'tool_result must be preceded by a tool_call') {
console.error('Stream was joined late or tool_call chunk was dropped — restart consumption from the beginning');
}
throw err;
} Prevention
- Consume streams from the first chunk or a checkpoint containing tool_call parts
- Never drop or reorder tool_call chunks in middleware
- Initialize toolInvocations defensively when processing partial message history
- Keep client and server stream protocol versions aligned
When it happens
Trigger: Consuming a partially persisted or truncated message stream where the tool_call chunk was missed (joined mid-stream, resumed from an offset, or the server emitted results out of order); feeding historical/loaded messages through the processor without their preceding tool_call chunks.
Common situations: Reconnecting to a thread stream after the tool call already streamed; custom servers or middleware dropping tool_call chunks; loading stored threads whose serialization omitted toolInvocations; version mismatches where the client expects toolInvocations but the server emits a different shape.
Related errors
- ${error.message}
- AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED
- 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
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/195f630f849822d0.
Report an issue: GitHub.