[BUG] tool_addition blocks are sent to a custom ANTHROPIC_BASE_URL, rejected with 400, and silently retried on every session start

Status Open
Reported on v2.1.274
Maintainer reply None cached
Activity 2 comments · opened Sep 17, 2026
Correction from the author (2026-09-17): the cause given below is wrong. No request from Claude Code contains a tool without input_schema. The first request is rejected because it carries tool_addition content blocks (beta mid-conversation-tool-changes-2026-07-01) that the endpoint does not accept; the silent retry drops the blocks and the beta header. Captured request, headers and checks are in this comment. The symptom described below is accurate; the cause, the quoted curl error and the ~163-entry extrapolation are not. Original text kept unchanged for the record.

Preflight Checklist

  • [x] I have searched existing issues. #89211 reports the same root cause with a different symptom; this is a separate manifestation, described below.
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.274)

What's Wrong?

Related to #89211, different symptom, and quieter.

With ENABLE_TOOL_SEARCH=true behind a custom ANTHROPIC_BASE_URL, the first request of every session carries deferred tool entries that have no input_schema. A strict Anthropic-compatible server rejects the whole request at validation. Claude Code then swallows the 400, resends with full schemas, and the retry succeeds.

The session completes normally. Nothing appears on the command line, claude -p --output-format json reports is_error: false, and the transcript records no error. The only trace is server-side.

#89211 describes the visible form of this, where the provider returns a specific "Deferred custom tools are only supported on…" message and the session stalls until the user repeatedly types continue. Against vLLM the failure is invisible instead, so the cost is paid silently on every session start: one rejected round-trip plus a ~20 KB error body.

What Should Happen?

Option 1 from #89211 — detect whether the endpoint handles deferred tools before enabling tool search — would fix this case.

Option 3 from that report, a startup warning, would not help here. Nothing looks wrong to the user, so a warning about a failure they never see is easy to dismiss.

A third possibility specific to this shape: omit schema-less entries from tools[] entirely until they are revealed, rather than sending names that a strict server cannot validate.

Error Messages/Logs

Proxy log in front of the server, showing the rejection and its size:

[INFO] Request ... "POST /v1/messages HTTP/1.1" 400 20263 "claude-cli/2.1.274 (external, sdk-cli)" 65.576149ms
[INFO] Request ... "POST /v1/messages HTTP/1.1" 200 11151 "claude-cli/2.1.274 (external, sdk-cli)" 12.540035545s

Server log, same connection, 400 then 200:

INFO:  ... - "POST /v1/messages?beta=true HTTP/1.1" 400 Bad Request
INFO:  ... - "POST /v1/messages?beta=true HTTP/1.1" 200 OK

The server's validation error, reproduced directly with curl:

{"error":{"message":"1 validation error:\n  {'type': 'missing', 'loc': ('body', 'tools', 0, 'input_schema'), 'msg': 'Field required', 'input': {'type': 'tool_search_tool_20250101', 'name': 'tool_search'}}","type":"Bad Request","param":"body.tools.0.input_schema","code":400}}

Measured over one afternoon on 2.1.273: 29 × 200 and 3 × 400. Each 400 landed on the first request of one of the three sessions that announced deferred tools, matching to the second; the four sessions without deferred tools produced none. All three bodies were 20214 bytes. After upgrading to 2.1.274 the behaviour is unchanged (the 20263-byte rejection above).

I did not capture the request body. Sending N name-only tools to the same server grows the error body linearly at ~123 bytes each (1 → 234, 10 → 1343, 30 → 3823, 60 → 7543, 100 → 12504), so 20214 bytes extrapolates to roughly 163 entries; the session transcript recorded total_deferred_tools: 148.

Steps to Reproduce

  1. Serve a model behind an Anthropic-compatible endpoint that requires input_schema on every tools[] entry (vLLM's /v1/messages does; AnthropicTool.input_schema in vllm/entrypoints/anthropic/protocol.py is a required field).
  2. Point Claude Code at it with ANTHROPIC_BASE_URL, and set ENABLE_TOOL_SEARCH=true.
  3. Run any task, for example claude -p "list the .py files in this directory".
  4. The session completes normally.
  5. Check the server log. The first request of the session was rejected with 400 and immediately retried.

Server-side tracking issue: vllm-project/vllm#57324.

Claude Model

Other

Is this a regression?

I don't know

Claude Code Version

2.1.274

Platform

Other

Operating System

macOS

Terminal/Shell

Other

Additional Information

Endpoint is vLLM serving Qwen3.8-Flash-Next, --max-model-len 262144 --enable-prefix-caching, with llama-swap in front.

Two observations from the same setup, both minor next to the above:

contextWindow is reported as 200000 in the session envelope while the server was started with --max-model-len 262144, because the model name is not in the client's catalog. The startup warning names behavesAs and CLAUDE_CODE_MAX_CONTEXT_TOKENS as remedies; I have not tested either.

Whether tool search is worth enabling here is a separate question from this bug. On this setup it measured cheaper overall despite the extra round-trip: at equal work (6 turns, 5 engine requests, same final answer), 11,046 tokens of real prefill with it on against 16,605 with it off, even though the prefix cache hit rate fell from 94% to 80%.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗