Skip to content

fix: normalize null tool input from Anthropic adapter#266

Merged
AlemTuzlak merged 5 commits intoTanStack:mainfrom
KrunchMuffin:fix/null-tool-input-normalization
Feb 7, 2026
Merged

fix: normalize null tool input from Anthropic adapter#266
AlemTuzlak merged 5 commits intoTanStack:mainfrom
KrunchMuffin:fix/null-tool-input-normalization

Conversation

@KrunchMuffin
Copy link
Contributor

@KrunchMuffin KrunchMuffin commented Feb 6, 2026

#265

Summary

When Claude occasionally produces a tool_use content block with no input_json_delta events (or with "null" as the partial JSON), the Anthropic adapter's content_block_stop handler passes null as the parsed input instead of defaulting to {}.

This causes executeToolCalls to fail Zod schema validation (since null isn't an object). The error result is correctly captured, but the agent loop stalls after the failed tool execution — the model either doesn't produce a follow-up text response, or the follow-up text isn't emitted through the stream.

The end result for users: the assistant says "Let me search for that..." then silence. No error, no follow-up.

Root Cause

In @tanstack/ai-anthropicadapters/text.ts

The processAnthropicStream method, inside the content_block_stop handler:

parsedInput = existing.input ? JSON.parse(existing.input) : {}

When existing.input is the string "null" (from the model streaming partial_json: "null"), JSON.parse("null") returns JavaScript null — which passes the truthy check but isn't {}. The resulting TOOL_CALL_END event has input: null.

In @tanstack/aitools/tool-calls.ts

Downstream in executeToolCalls:

  1. toolCall.function.arguments is "null" (set by completeToolCall via JSON.stringify(null))
  2. argsStr = "null".trim() || "{}""null" (truthy, so no fallback)
  3. JSON.parse("null")null
  4. parseWithStandardSchema(tool.inputSchema, null) → Zod throws (null is not an object)
  5. Error result pushed, fed back to model
  6. Agent loop stalls — no follow-up text produced

Changes

Anthropic adapter (content_block_stop): Normalize parsed input to {} when JSON.parse produces null or any non-object value:

const parsed = existing.input ? JSON.parse(existing.input) : {}
parsedInput = parsed && typeof parsed === 'object' ? parsed : {}

Anthropic adapter (formatMessages): Same normalization when reconstructing tool calls for conversation history (prevents input: null being sent back to the Anthropic API on subsequent turns).

executeToolCalls + ToolCallManager.executeTools: Normalize "null" argument strings to "{}" before JSON parsing — a defense-in-depth fix for the same issue at the tool execution layer.

How to Reproduce

This is intermittent and depends on model behavior. We've observed it with claude-sonnet-4-5 when the model generates a tool_use block with no meaningful arguments (e.g., after a first tool call that returned zero results). The model seems to "start" a follow-up tool call but doesn't commit to arguments.

Typical pattern:

  1. User asks a question that triggers a tool call
  2. Model produces text + first tool_use (with valid arguments) + second tool_use (with null/empty arguments)
  3. First tool executes successfully, second fails validation
  4. Agent loop feeds error back to model
  5. No follow-up text is produced — stream ends silently

Example Session Data

{
  "role": "assistant",
  "content": "Let me search for disc mowers near you in Saskatoon:",
  "toolCalls": [
    {
      "id": "toolu_1770336584303_0",
      "type": "function",
      "function": {
        "name": "search_equipment",
        "arguments": "null"
      }
    }
  ]
}

Test Plan

  • Verified fix handles JSON.parse("null") → normalizes to {}
  • Verified fix handles empty existing.input → defaults to {}
  • Verified normal tool calls with valid JSON input are unaffected
  • All three fix locations use consistent normalization logic

Summary by CodeRabbit

  • Bug Fixes
    • Tool argument parsing now treats empty, whitespace-only, and the literal "null" as empty objects; JSON parse errors are avoided and non-object parse results default to an empty object.
  • Chores
    • Patch version bumps recorded for affected packages.

When Claude occasionally produces a tool_use content block with no
input_json_delta events (or with "null" as the partial JSON),
JSON.parse("null") returns JavaScript null — which passes the truthy
check but isn't {}. This causes downstream Zod schema validation to
fail, and the agent loop stalls silently.

Changes:
- Anthropic adapter (content_block_stop): normalize parsed input to {}
  when JSON.parse produces null or a non-object value
- Anthropic adapter (formatMessages): same normalization when
  reconstructing tool calls for conversation history
- executeToolCalls + ToolCallManager.executeTools: normalize "null"
  argument strings to "{}" before parsing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Validated and normalized JSON inputs for tool-handling: the Anthropic text adapter now ensures parsed JSON is an object before assignment, and tool-call handlers trim input, convert the literal "null" and empty/whitespace to "{}", then parse safely to avoid non-object or empty inputs being used.

Changes

Cohort / File(s) Summary
Anthropic Text Adapter
packages/typescript/ai-anthropic/src/adapters/text.ts
Ensure parsed JSON is an object; fallback to {} when parsing fails or result is not an object.
Tool Call Argument Parsing
packages/typescript/ai/src/activities/chat/tools/tool-calls.ts
Normalize tool argument strings by trimming, treating empty/whitespace or the literal 'null' as '{}', then JSON.parse; improves robustness and error messaging.
Changeset
.changeset/shaky-heads-enjoy.md
Add patch bumps for @tanstack/ai-anthropic and @tanstack/ai with message "fix for tool calls".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • harry-whorlow

Poem

🐇 I trimmed the inputs, nudged null to braces bright,
Small hops of objects keep the parser light.
No stray values wandering through the gate,
Now every tool gets valid mate.
🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix: normalize null tool input from Anthropic adapter' clearly and concisely summarizes the main change: normalizing null tool input from the Anthropic adapter to prevent validation failures.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering root cause analysis, detailed changes, reproduction steps, and test plan. It includes a changeset file indicating patch version updates with the message 'fix for tool calls'.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@nx-cloud
Copy link

nx-cloud bot commented Feb 6, 2026

View your CI Pipeline Execution ↗ for commit 0b229d3

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

☁️ Nx Cloud last updated this comment at 2026-02-07 14:58:32 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 7, 2026

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-devtools-core

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: 0b229d3

@AlemTuzlak AlemTuzlak merged commit 6e1bb50 into TanStack:main Feb 7, 2026
6 checks passed
@github-actions github-actions bot mentioned this pull request Feb 7, 2026
@KrunchMuffin KrunchMuffin deleted the fix/null-tool-input-normalization branch February 7, 2026 19:17
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.

3 participants