Skip to content

Cancelled/denied tool calls are indistinguishable from genuine failures #1386

Description

@oztune

TanStack AI version

@tanstack/ai v0.54.0 (also v0.53.0)

Framework/Library version

Framework-agnostic (plain Node.js)

Describe the bug and the steps to reproduce it

A cancelled or user-denied tool call is emitted with the same shape as a genuine failure, so a consumer can't tell them apart. Both produce state: 'output-error' + { error: <hardcoded string> } (packages/ai/src/activities/chat/tools/tool-calls.ts):

  • Cancel (:807-813): { error: 'Tool execution cancelled' }
  • Deny (:912-923, :1001-1009, via deniedApprovalResult :486-490): { error: 'User declined tool execution' }

That is identical to real failures — unknown tool (:787), arg-parse (:826), validation (:848). The only discriminator is the hardcoded English string; there's no distinct state, since ToolResultState is 'streaming' | 'complete' | 'error' (types.ts:76).

The framework does know the outcome structurally — the InterruptStore record's status is 'cancelled' — but that's discarded when the TOOL_CALL_RESULT is built, and only { error: <string> } survives into the persisted ModelMessage a UI re-hydrates from. So after reload, matching that localizable string is the only option left.

Runnable repro (no API key) + verified output are in the linked sandbox: an approval-deny and a genuine execute() throw come back byte-identical except for the error string.

Expected: a structured, persistable signal for "user cancelled/declined", distinct from a failure — a terminal state, a discriminator on the result, or (minimally) exported constants for the two strings. Ideally carry through the outcome the InterruptStore already records.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://stackblitz.com/github/oztune/tanstack-ai-cancel-repro

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

No response

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