TanStack AI version
@tanstack/ai 0.58.0. Also 0.55.0. The code is the same on main.
Framework/Library version
Framework-agnostic (Node 24)
Describe the bug and the steps to reproduce it
A server tool call fails when the handler throws, when the input fails its schema, or when the tool does not exist. chat() streams the TOOL_CALL_RESULT with metadata.tanstack.state: 'output-error'. The client then shows a tool-result part with state: 'error' and error, as the client tools docs describe.
But the tool message that chat() adds to its message history has no error:
{ role: 'tool', content: '{"error":"lookup is down"}', toolCallId: 'call_1' }
Middleware reads that history in onFinish, and @tanstack/ai-persistence saves it. On a reload, modelMessagesToUIMessages gives the tool-result part state: 'error' only when msg.error is set (messages.ts#L1128-L1141). As a result, the part comes back with state: 'complete' and no error. The UI shows the call as failed while the run streams, and as successful after a reload.
buildToolResultChunks uses result.state to tag the chunk, but not to build the message (index.ts#L3157-L3185).
Steps:
- In the linked repo, run
npm install && npm run repro. No API key is necessary. A scripted adapter makes the tool call.
- For each of the three failures, the script prints the streamed metadata, the saved tool message, and the rebuilt
tool-result part. This is the output for the handler that throws:
## handler throws
1. streamed TOOL_CALL_RESULT metadata: { tanstack: { state: 'output-error' } }
2. saved tool message: {
role: 'tool',
content: '{"error":"lookup is down"}',
toolCallId: 'call_1'
}
3. rebuilt tool-result part: { state: 'complete', error: undefined }
Expected: the saved tool message has error, and the rebuilt part is { state: 'error', error: 'lookup is down' }. That is the same state that the stream processor sets live (processor.ts#L1655-L1682).
When result.state === 'output-error', set error on that message. This fixes all three cases in the repro. A PR follows.
Related: #1386 and #1401 are about denied and cancelled calls. #1401 adds toolResultOutcome to their metadata. It does not mark a plain failure. I ran a test of the three cases against the head of #1401, and all three still rebuild as complete. Both change the same block of code.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://github.com/aayushbtw/tanstack-ai-tool-error-repro
Do you intend to try to help solve this bug with your own PR?
Yes, I am also opening a PR that solves the problem along side this issue
Terms & Code of Conduct
TanStack AI version
@tanstack/ai 0.58.0. Also 0.55.0. The code is the same on
main.Framework/Library version
Framework-agnostic (Node 24)
Describe the bug and the steps to reproduce it
A server tool call fails when the handler throws, when the input fails its schema, or when the tool does not exist.
chat()streams theTOOL_CALL_RESULTwithmetadata.tanstack.state: 'output-error'. The client then shows atool-resultpart withstate: 'error'anderror, as the client tools docs describe.But the tool message that
chat()adds to its message history has noerror:Middleware reads that history in
onFinish, and@tanstack/ai-persistencesaves it. On a reload,modelMessagesToUIMessagesgives thetool-resultpartstate: 'error'only whenmsg.erroris set (messages.ts#L1128-L1141). As a result, the part comes back withstate: 'complete'and noerror. The UI shows the call as failed while the run streams, and as successful after a reload.buildToolResultChunksusesresult.stateto tag the chunk, but not to build the message (index.ts#L3157-L3185).Steps:
npm install && npm run repro. No API key is necessary. A scripted adapter makes the tool call.tool-resultpart. This is the output for the handler that throws:Expected: the saved tool message has
error, and the rebuilt part is{ state: 'error', error: 'lookup is down' }. That is the same state that the stream processor sets live (processor.ts#L1655-L1682).When
result.state === 'output-error', seterroron that message. This fixes all three cases in the repro. A PR follows.Related: #1386 and #1401 are about denied and cancelled calls. #1401 adds
toolResultOutcometo their metadata. It does not mark a plain failure. I ran a test of the three cases against the head of #1401, and all three still rebuild ascomplete. Both change the same block of code.Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://github.com/aayushbtw/tanstack-ai-tool-error-repro
Do you intend to try to help solve this bug with your own PR?
Yes, I am also opening a PR that solves the problem along side this issue
Terms & Code of Conduct