Skip to content

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

Open
tombeckenham wants to merge 3 commits intomainfrom
532-tool-name-is-undefined-in-messages-after-executing-a-tool-that-needs-approval
Open

fix(ai): tool_use.name undefined after approving a tool then sending follow-up (#532)#536
tombeckenham wants to merge 3 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

    • Fixed an error that occurred when sending follow-up messages after approving a tool call.
    • Ensured approved tool executions replace placeholder messages with real tool results to avoid duplicate/discarded outputs.
    • Restored compatibility so tool-call names are emitted in the new spec field while still supporting the legacy alias.
  • Tests

    • Added E2E and unit tests validating approval flows, follow-up message 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>
@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: 2a40b55d-4b49-49b5-8cd9-b066246a0ca5

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • testing/e2e/package.json
  • testing/e2e/src/lib/providers.ts
  • testing/e2e/tests/tool-approval.spec.ts

📝 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 clearly and specifically identifies the main bug fix: resolving the undefined tool_use.name error after approving a tool and sending a follow-up message, with issue reference #532.
Description check ✅ Passed The description comprehensively covers the root cause, all code changes across multiple files, testing additions, and correctly fills all sections of the template with thorough explanations.
Linked Issues check ✅ Passed The pull request fully addresses all coding objectives from #532: populates toolCallName for non-empty tool_use.name, replaces placeholder tool messages with real results, ensures immediate tool result delivery post-approval, adds comprehensive unit and E2E tests, and preserves Anthropic compatibility.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing #532: core logic fixes in chat/index.ts and stream/processor.ts, targeted test additions, E2E scenarios, and supporting OpenRouter test infrastructure changes are all necessary for the fix or its validation.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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

3 package(s) bumped directly, 30 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

🟩 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-isolate-cloudflare 0.1.8 → 0.1.9 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 82ea764

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 30s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-07 06:20:19 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: 82ea764

@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>
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

1 participant