Skip to content

Failed server tool call is saved without error, so it reloads as complete #1470

Description

@aayushbtw

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:

  1. In the linked repo, run npm install && npm run repro. No API key is necessary. A scripted adapter makes the tool call.
  2. 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

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

has-prAn open PR references this issuewaiting-on: maintainerThe ball is in the maintainers’ court

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions