Skip to content

ChatClient.stop() allows a pending client tool to start a native resume #1214

Description

@kolaworld

TanStack AI version

0.48.0

Framework/Library version

@tanstack/ai-client: 0.26.0 | @tanstack/ai-svelte: 0.18.3

Describe the bug and the steps to reproduce it

ChatClient.stop() aborts the current request, but it does not invalidate client-tool executions or native interrupt resolutions that are still pending for that turn.

When an automatically executed client tool settles after stop(), its result can start another connection and continue the conversation.

onToolCall starts and tracks the client-tool promise. A fulfilled tool is passed to addToolResultForClientTool() as output-available, while a rejected tool is caught and passed to the same method as output-error.

stop() aborts the active connection, discards queued sends and resets the local generating state. It does not invalidate the pending tool promise or the interrupt manager state associated with that turn.

When the tool later settles, addToolResultForClientTool() resolves the matching native client-tool interrupt. The interrupt manager then starts an asynchronous batch submission, and submitInterruptBatch() starts a child resume run.

The resulting sequence is:

RUN_FINISHED (outcome: interrupt)
client tool remains pending
client.stop()
client tool settles
connect(... resume ...) starts after Stop

Expected behavior

Calling ChatClient.stop() while a client tool is pending should prevent asynchronous continuation work from that turn from starting another connection. Late client-tool success or error should be ignored before it mutates the transcript, and deferred or in-flight native interrupt submissions should settle without restoring an error or resuming the stopped turn.

ChatClient should track a continuation generation across client-tool execution and native resume submission. stop() should invalidate that generation, clear lastResume and the active interrupt submission, and reset the interrupt manager. The interrupt manager should also invalidate its active submission on reset so a late submission result cannot publish stale state.

After Stop, the client should preserve the transcript accumulated before Stop, leave resuming false and allow a later explicit user message to start without resume or parentRunId. The underlying client-tool promise does not need to be cancelled because arbitrary tools may not support cancellation.

This issue covers the client lifecycle only. ChatClient.stop() does not cancel a durable server run or clear server-authoritative persisted interrupts; that requires a separate server cancellation path.

Related issues

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://stackblitz.com/edit/vitejs-vite-l3sbbrcs?file=src%2Fmain.ts

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.

Metadata

Metadata

Assignees

No one assigned

    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