anthropics / anthropics/claude-code
[BUG] Deferred tools are sent without input_schema, rejected, and silently retried behind a custom ANTHROPIC_BASE_URL
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### 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%.
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce with ENABLE_TOOL_SEARCH=true and a custom ANTHROPIC_BASE_URL against an Anthropic-compatible endpoint that requires input_schema, then inspect the first /v1/messages request and its retry. The vLLM validation requirement is documented in vllm/entrypoints/anthropic/protocol.py. Done means session startup no longer sends a rejected request for deferred tools while preserving successful tool-search behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100