Skip to content

fix(ai): tool_use.name undefined after approving a tool then sending follow-up (#532)#536

Merged
AlemTuzlak merged 4 commits intomainfrom
532-tool-name-is-undefined-in-messages-after-executing-a-tool-that-needs-approval
May 8, 2026
Merged

fix(ai): tool_use.name undefined after approving a tool then sending follow-up (#532)#536
AlemTuzlak merged 4 commits intomainfrom
532-tool-name-is-undefined-in-messages-after-executing-a-tool-that-needs-approval

Conversation

@tombeckenham
Copy link
Copy Markdown
Contributor

@tombeckenham tombeckenham commented May 7, 2026

Closes #532

🎯 Changes

Fixes the 400 ValidationException: messages.N.content.M.tool_use.name: String should have at least 1 character error from Anthropic when sending a follow-up message after a tool that needed approval has been approved and executed.

Root cause. buildToolResultChunks (in packages/typescript/ai/src/activities/chat/index.ts) synthesizes a TOOL_CALL_START chunk for the post-approval continuation flow. It only set the deprecated toolName field, not the AG-UI spec field toolCallName. The client's StreamProcessor.handleToolCallStartEvent reads chunk.toolCallName, got undefined, and wrote a tool-call part with name: undefined. On the next user message, that propagated through uiMessageToModelMessages into a ModelMessage, the Anthropic adapter wrote it into tool_use.name, and the API rejected the request.

Changes:

  • packages/typescript/ai/src/activities/chat/index.ts — synthesized TOOL_CALL_START now includes toolCallName alongside the deprecated toolName alias. After running an approved tool server-side, the agent loop now replaces the pendingExecution: true placeholder tool message in its history instead of appending a duplicate — this stops the Anthropic adapter's tool_result de-dup (first-wins) from discarding the real result, so the model sees the actual tool output during the post-approval streaming response.
  • packages/typescript/ai/src/activities/chat/stream/processor.ts — defensive: handleToolCallStartEvent now falls back to chunk.toolName when chunk.toolCallName is missing, since toolName is documented as a deprecated alias.
  • packages/typescript/ai/tests/chat.test.ts — new unit test simulates the full approved-UIMessage flow and asserts both toolCallName/toolName on the synthesized chunk and that the adapter sees the real tool result, not the placeholder. Three existing TOOL_CALL_START assertions also now check toolCallName.
  • packages/typescript/ai/tests/stream-processor.test.ts — new unit test covers the deprecated-only toolName chunk path.
  • testing/e2e/tests/tool-approval.spec.ts + testing/e2e/fixtures/tool-approval/approval.json — new E2E case approves the tool then sends a follow-up message; matching aimock fixture entry added.

Note on emitting both toolCallName and toolName. The fix continues to emit both fields rather than dropping the deprecated alias. Two reasons: (1) every other emission site already emits both — TOOL_CALL_END two lines below in the same function, and all three TOOL_CALL_START/END sites in the Anthropic adapter — so the bug was just that this one site was inconsistent; (2) toolName is @deprecated but documented as "Kept for backward compatibility", so dropping it would be a breaking change for any middleware, devtools subscriber, or downstream consumer still reading it. Retiring toolName entirely (drop from every adapter, drop from the type, major bump) is out of scope for this bug fix and would be its own follow-up PR.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixes follow-up message errors after approving a tool call by ensuring emitted tool-call names use the new spec field while still supporting the legacy alias.
    • Approved tool executions now replace pending placeholders with the real tool result to prevent duplicates or discarded outputs.
  • Tests

    • Added/updated unit, regression and E2E tests and fixtures to validate approval flows, follow-up handling, name-field compatibility, and placeholder replacement.

…follow-up (#532)

Synthesized TOOL_CALL_START in the post-approval continuation now sets
the AG-UI spec field `toolCallName` (in addition to the deprecated
`toolName` alias), so the client's StreamProcessor records a tool-call
part with a defined `name` instead of `undefined`. Without the fix, the
next outbound request was rejected by Anthropic with
`tool_use.name: String should have at least 1 character`.

Defensively, StreamProcessor now also falls back to `chunk.toolName` when
`chunk.toolCallName` is missing.

Additionally, after running an approved tool server-side, the agent loop
replaces the `pendingExecution: true` placeholder tool message in its
message history instead of appending a duplicate. This stops the
Anthropic adapter's tool_result de-dup (which keeps the first match)
from discarding the real result, so the model sees the actual tool
output during the post-approval streaming response.

Includes unit tests (chat.test.ts, stream-processor.test.ts) and an E2E
case in tool-approval.spec.ts plus a matching aimock fixture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tombeckenham tombeckenham linked an issue May 7, 2026 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc415c61-73ba-4342-b2da-1799c43fc0cb

📥 Commits

Reviewing files that changed from the base of the PR and between 82ea764 and f620271.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • testing/e2e/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • testing/e2e/package.json

📝 Walkthrough

Walkthrough

This PR ensures TOOL_CALL_START events include a defined toolCallName (falling back to deprecated toolName) and replaces pendingExecution placeholder tool messages with actual tool results so downstream adapters receive the real tool result in the same stream.

Changes

Tool Name & Message Deduplication Fix

Layer / File(s) Summary
Protocol Contract & Field Fallback
.changeset/fix-tool-name-undefined-after-approval.md, packages/typescript/ai/src/activities/chat/stream/processor.ts
Changeset documents the fix. StreamProcessor now extracts tool name from toolCallName with fallback to deprecated toolName, ensuring TOOL_CALL_START supplies a defined name.
Message History Deduplication
packages/typescript/ai/src/activities/chat/index.ts
buildToolResultChunks detects and replaces placeholder tool messages (JSON pendingExecution: true) for the same toolCallId with the real tool result, or appends if none exists.
Stream Processor Integration
packages/typescript/ai/src/activities/chat/stream/processor.ts, packages/typescript/ai/tests/stream-processor.test.ts
Tool-call state creation and registration remain; tests validate fallback behavior when only deprecated toolName is present.
Unit Test Coverage
packages/typescript/ai/tests/chat.test.ts
Add assertions that TOOL_CALL_START includes toolCallName (and toolName alias) across pending-tool scenarios and a regression test ensuring approval-flow replaces placeholder with the real tool result.
E2E Providers & Fixtures
testing/e2e/package.json, testing/e2e/src/lib/providers.ts, testing/e2e/fixtures/tool-approval/approval.json
Add @openrouter/sdk, update OpenRouter adapter to inject X-Test-Id via HTTPClient hook, and add a follow-up fixture entry for approval flows.
End-to-End Tests
testing/e2e/tests/tool-approval.spec.ts
Add follow-up E2E test: approve addToCart, wait for "added", send follow-up, assert assistant responds with "follow-up".

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant StreamProcessor
  participant TextEngine
  participant MessageHistory
  participant Adapter

  User->>StreamProcessor: sends approval response / follow-up
  StreamProcessor->>TextEngine: request tool execution handling
  TextEngine->>MessageHistory: search for pendingExecution placeholder
  alt Placeholder found
    TextEngine->>MessageHistory: replace with real tool result
  else
    TextEngine->>MessageHistory: append real tool result
  end
  TextEngine-->>Adapter: emit tool message (with defined tool name)
  Adapter-->>User: assistant response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped in the stream with a curious cheer,
A missing tool name caused trouble, oh dear.
I swapped the placeholder, let real results shine,
Now follow-ups flow and the names all align.
Hooray — no more undefineds, all's clear!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title precisely summarizes the main fix: resolving undefined tool_use.name when sending a follow-up after approving a tool, and references the issue number.
Description check ✅ Passed The description fully covers changes across all modified files, explains root cause and solutions clearly, addresses both checklist items, and includes a detailed note on backward compatibility.
Linked Issues check ✅ Passed Code changes comprehensively address all objectives from #532: preserving tool-call metadata via toolCallName field, replacing placeholder messages with real results, preventing empty tool_use.name, and adding unit/E2E tests.
Out of Scope Changes check ✅ Passed All changes directly support the fix for #532: core logic fixes in chat/index.ts and processor.ts, related test additions, E2E scenarios, and provider configuration updates for testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 532-tool-name-is-undefined-in-messages-after-executing-a-tool-that-needs-approval

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

🚀 Changeset Version Preview

4 package(s) bumped directly, 29 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-anthropic 0.8.3 → 1.0.0 Changeset
@tanstack/ai-code-mode 0.1.8 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.1.8 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.0 → 1.0.0 Dependent
@tanstack/ai-event-client 0.2.8 → 1.0.0 Dependent
@tanstack/ai-fal 0.7.0 → 1.0.0 Dependent
@tanstack/ai-gemini 0.10.0 → 1.0.0 Dependent
@tanstack/ai-grok 0.7.0 → 1.0.0 Dependent
@tanstack/ai-groq 0.1.8 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.8 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.8 → 1.0.0 Dependent
@tanstack/ai-ollama 0.6.10 → 1.0.0 Dependent
@tanstack/ai-openai 0.8.2 → 1.0.0 Dependent
@tanstack/ai-openrouter 0.8.2 → 1.0.0 Dependent
@tanstack/ai-preact 0.6.20 → 1.0.0 Dependent
@tanstack/ai-react 0.8.0 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.6.2 → 1.0.0 Dependent
@tanstack/ai-solid 0.7.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.6.2 → 1.0.0 Dependent
@tanstack/ai-svelte 0.7.0 → 1.0.0 Dependent
@tanstack/ai-vue 0.7.0 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.14.0 → 0.15.0 Changeset
@tanstack/ai-client 0.8.0 → 0.9.0 Changeset
@tanstack/ai-isolate-cloudflare 0.1.8 → 0.2.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-code-mode-models-eval 0.0.12 → 0.0.13 Dependent
@tanstack/ai-devtools-core 0.3.25 → 0.3.26 Dependent
@tanstack/ai-vue-ui 0.1.31 → 0.1.32 Dependent
@tanstack/preact-ai-devtools 0.1.29 → 0.1.30 Dependent
@tanstack/react-ai-devtools 0.2.29 → 0.2.30 Dependent
@tanstack/solid-ai-devtools 0.2.29 → 0.2.30 Dependent
ts-svelte-chat 0.1.38 → 0.1.39 Dependent
ts-vue-chat 0.1.38 → 0.1.39 Dependent
vanilla-chat 0.0.35 → 0.0.36 Dependent

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 7, 2026

View your CI Pipeline Execution ↗ for commit f620271

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 6m 3s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 1m 45s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-08 11:06:01 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 7, 2026

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@536

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@536

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@536

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@536

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@536

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@536

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@536

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@536

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@536

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@536

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@536

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@536

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@536

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@536

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@536

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@536

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@536

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@536

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@536

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@536

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@536

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@536

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@536

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@536

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@536

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@536

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@536

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@536

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@536

commit: f620271

@tombeckenham tombeckenham requested a review from a team May 7, 2026 05:52
tombeckenham and others added 2 commits May 7, 2026 15:56
Openrouter's chatStream fails on the multi-turn approval-then-followup
flow for reasons unrelated to the core fix in @tanstack/ai. The unit
tests in packages/typescript/ai cover the fix provider-agnostically;
the other five tool-approval providers (openai, anthropic, ollama, groq,
grok) still exercise it in E2E.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenRouter's adapter previously received the testId via a query param on
serverURL, but the SDK calls `new URL(path, baseURL)` per request, which
drops the search component — so testId never reached aimock and every
openrouter test collided on the `__default__` testId bucket. As soon as
two openrouter tests sent the same userMessage, the second one's
sequenceIndex didn't reset, no fixture matched, and chatStream threw.

The OpenRouter SDK exposes an `httpClient` option whose `HTTPClient`
supports `addHook("beforeRequest", …)`. Use that to set X-Test-Id on
each request. This isolates openrouter tests the same way every other
provider already is.

Also reverts the earlier `test.skip(provider === 'openrouter')` on the
issue #532 follow-up case — with this fix the test passes for openrouter
too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fined-in-messages-after-executing-a-tool-that-needs-approval

# Conflicts:
#	pnpm-lock.yaml
#	testing/e2e/package.json
@AlemTuzlak AlemTuzlak merged commit a4e2c55 into main May 8, 2026
8 checks passed
@AlemTuzlak AlemTuzlak deleted the 532-tool-name-is-undefined-in-messages-after-executing-a-tool-that-needs-approval branch May 8, 2026 11:15
@github-actions github-actions Bot mentioned this pull request May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool name is undefined in messages after executing a tool that needs approval

2 participants