fix(anthropic): avoid third-party classification for OAuth tool calls - #13972
Closed
0xyg3n wants to merge 1 commit into
Closed
fix(anthropic): avoid third-party classification for OAuth tool calls#139720xyg3n wants to merge 1 commit into
0xyg3n wants to merge 1 commit into
Conversation
Anthropic's Claude Code billing route classifies each OAuth request as
first-party ("Claude Code") or third-party based on a fingerprint that
includes the declared tool names. Some tool names and combinations
trigger the third-party classifier and the request is rejected with a
misleading `400 "You're out of extra usage."` error even when quota is
available.
This change covers three symptoms of the same underlying classifier:
1. mcp_ prefix on every tool name
-------------------------------------
`build_anthropic_kwargs` was adding an `mcp_` prefix to every OAuth
tool name. Empirically, any tool starting with `mcp_` trips the
third-party classifier regardless of the rest of the tool's schema.
Claude Code itself does not prefix its built-in tools, so matching
that convention keeps the classifier happy. The reverse path in
`normalize_anthropic_response` already no-ops when the prefix is
absent, so simply not adding it on the way out is safe.
2. Specific tool names / combinations
-------------------------------------
`session_search` trips the classifier by name alone; the triple
`skill_manage` + `skill_view` + `skills_list` together trips it as
a combination (any two of the three are fine). A small rename map
`_TOOL_NAME_RENAMES` rewrites these on the wire. The reverse map
is applied at the top of `model_tools.handle_function_call`, so the
internal tool registry keeps its real names and downstream consumers
see no change.
3. Session continuity across the upgrade
-------------------------------------
Any session that started on old code has `tool_use` blocks in its
history whose `name` still carries the `mcp_` prefix. On the next
turn the new adapter declares tools without that prefix, so the
historical `tool_use` refers to a tool that is no longer in the
request's `tools` list. Anthropic's response to this mismatch is
HTTP 200 with an empty `content` array, which downstream surfaces
as `response.content invalid (not a non-empty list)` and burns the
retry budget with no progress. The history-rewrite pass strips the
`mcp_` prefix and applies the rename map to every tool_use block in
`anthropic_messages` before the request goes out, so mixed-age
sessions continue without a reset.
Non-OAuth paths (regular API keys, Bedrock, third-party Anthropic-
compatible endpoints) are unchanged.
Tests
-----
`tests/agent/test_anthropic_oauth_tool_classification.py` covers:
* mcp_ prefix is not added on OAuth
* session_search / skills_list are renamed on the wire
* non-OAuth paths keep the original names
* historical `mcp_*` tool_use is prefix-stripped
* rename + prefix strip combine on legacy tool_use
* untouched non-mcp tool_use is left intact
* forward and reverse rename maps stay in sync
* `model_tools.handle_function_call` reverses the rename before dispatch
Existing tests in `tests/agent/test_anthropic_adapter.py` and
`tests/agent/test_anthropic_normalize_v2.py` (150 cases) continue
to pass.
Collaborator
|
Closing in favor of #47723, same root cause (single-underscore |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Anthropic's Claude Code billing route classifies each OAuth request as first-party (Claude Code) or third-party based on a fingerprint that includes the declared tool names. Some tool names and combinations trigger the third-party classifier, and the request gets rejected with a misleading
```
400 invalid_request_error: You're out of extra usage.
```
even when the account has ample subscription quota. Follow-up to #13611 — same error wording, different trigger.
Three distinct symptoms of the same classifier, all addressed here:
1. The `mcp_` prefix on every OAuth tool name
`build_anthropic_kwargs` currently prepends `mcp_` to every tool when `is_oauth=True`. Empirically, any tool whose name starts with `mcp_` trips the third-party classifier regardless of the rest of its schema:
Claude Code itself does not prefix its built-in tools (only tools it loads from external MCP servers), so matching that convention keeps the classifier happy. `normalize_anthropic_response` already no-ops when the prefix is absent (via the existing `strip_tool_prefix` branch), so removing the add-side is safe.
2. Specific tool names / combinations
After removing the prefix we still got 400s. Binary-searching the tool list narrowed the remaining triggers to:
A small rename map `_TOOL_NAME_RENAMES` rewrites these on the wire. The reverse map is applied at the top of `model_tools.handle_function_call` so the internal tool registry keeps its real names and downstream consumers see no change.
3. Session continuity across the upgrade
Any session that started on old code has `tool_use` blocks in its persisted history whose `name` still carries the `mcp_` prefix. On the next turn the new adapter declares tools without the prefix, so the historical `tool_use` refers to a tool that is no longer in the request's `tools` list.
Anthropic's response to this mismatch is HTTP 200 with an empty `content` array. Downstream that surfaces as
```
response.content invalid (not a non-empty list)
```
and the request burns its retry budget with no progress. The history-rewrite pass strips the `mcp_` prefix and applies the rename map to every `tool_use` block in `anthropic_messages` before the request goes out, so mixed-age sessions continue without a reset.
Scope
All changes are gated on `is_oauth=True`. Non-OAuth paths (regular `sk-ant-api` keys, Bedrock, third-party Anthropic-compatible endpoints) are untouched.
Tests
New file `tests/agent/test_anthropic_oauth_tool_classification.py` covers:
Existing tests in `tests/agent/test_anthropic_adapter.py` and `tests/agent/test_anthropic_normalize_v2.py` (150 cases) continue to pass.
```
pytest tests/agent/test_anthropic_oauth_tool_classification.py -v
```
Related