{"record":{"id":"4c2ffc467e187782","repo":"mastra-ai/mastra","slug":"tool-result-must-be-preceded-by-a-tool-call-with-t","errorCode":null,"errorMessage":"tool_result must be preceded by a tool_call with the same toolCallId","messagePattern":"tool_result must be preceded by a tool_call with the same toolCallId","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client-sdks/client-js/src/resources/agent.ts","lineNumber":1585,"sourceCode":"            updateToolInvocationPart(value.toolCallId, invocation, (value as MaybeProviderMetadata).providerMetadata);\n\n            execUpdate();\n          }\n        }\n      },\n      onToolResultPart(value) {\n        const toolInvocations = message.toolInvocations;\n\n        if (toolInvocations == null) {\n          throw new Error('tool_result must be preceded by a tool_call');\n        }\n\n        // find if there is any tool invocation with the same toolCallId\n        // and replace it with the result\n        const toolInvocationIndex = toolInvocations.findIndex(invocation => invocation.toolCallId === value.toolCallId);\n\n        if (toolInvocationIndex === -1) {\n          throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');\n        }\n\n        const invocation = {\n          ...toolInvocations[toolInvocationIndex],\n          state: 'result' as const,\n          ...value,\n        } as const;\n\n        toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;\n\n        updateToolInvocationPart(\n          value.toolCallId,\n          invocation as ToolInvocation,\n          (value as MaybeProviderMetadata).providerMetadata,\n        );\n\n        execUpdate();\n      },","sourceCodeStart":1567,"sourceCodeEnd":1603,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/client-sdks/client-js/src/resources/agent.ts#L1567-L1603","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: filtering stream parts\nstream.processDataStream({ onChunk: c => { if (c.type !== 'tool-call') handle(c); } });\n// after: handle every part so toolInvocations is populated first\nstream.processDataStream({ onChunk: c => handle(c) });","handlingStrategy":"validation","validationCode":"function canApplyToolResult(message, toolCallId) {\n  return Array.isArray(message.toolInvocations) &&\n    message.toolInvocations.some(inv => inv.toolCallId === toolCallId);\n}\n// before merging a tool-result part: if (!canApplyToolResult(message, value.toolCallId)) skip or buffer it;","typeGuard":"function hasMatchingToolInvocation(message: UIMessage, toolCallId: string): boolean {\n  return Array.isArray(message.toolInvocations) &&\n    message.toolInvocations.some(i => i.toolCallId === toolCallId);\n}","tryCatchPattern":"try {\n  await stream.processDataStream({ onChunk, onFinish });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('tool_result must be preceded')) {\n    console.error('Inconsistent tool stream: result without matching call', err.message);\n  } else throw err;\n}","preventionTips":["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."],"tags":["streaming","tool-calls","state-machine"],"backgroundTag":"tool-result-without-matching-tool-call","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}