Conversation
… requests When an external client sends its own anthropic-beta header, the adapter was still merging in beta tags captured from the local claude CLI's outbound requests during detection. Those CLI defaults (e.g. context-1m-2025-08-07, advisor-tool-2026-03-01) then ended up on every proxied request — including ones targeting models or accounts that do not support those betas, which Anthropic rejects with HTTP 400. The client is authoritative for its own beta features. Now the CLI-detected anthropic-beta is only used as a fallback when the client did not send one of its own; otherwise we forward exactly (client tags + the required OAuth tags) and nothing else. Replaces the test that asserted the broken merge behavior with two tests covering the fallback path and the new client-authoritative path (verifying that CLI-only tags like context-1m-2025-08-07 do not leak in).
There was a problem hiding this comment.
Pull request overview
This PR fixes ClaudeAPIAdapter header handling so CLI-detected anthropic-beta tags captured during startup detection no longer leak into client-authoritative requests to the /claude endpoint, preventing invalid beta tags from being injected across unrelated models/accounts.
Changes:
- Treat client-provided
anthropic-betaas authoritative and skip merging CLI-detected beta tags when the client sends its own header. - Keep CLI-detected
anthropic-betaas a fallback only when the client omits the header entirely. - Update unit tests to reflect the new “authoritative client / CLI fallback” contract.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
ccproxy/plugins/claude_api/adapter.py |
Adjusts anthropic-beta merge logic to prevent CLI-detected beta leakage when the client provides its own header. |
tests/plugins/claude_api/unit/test_adapter.py |
Replaces the previous test asserting the broken behavior with two tests covering fallback vs. authoritative-client scenarios. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| client_beta = headers.get("anthropic-beta") or headers.get("Anthropic-Beta") | ||
| filtered_headers["anthropic-beta"] = self._merge_anthropic_beta(client_beta) | ||
| client_provided_beta = bool(client_beta) |
There was a problem hiding this comment.
client_provided_beta is computed via bool(client_beta), which treats an explicitly provided empty header value ("anthropic-beta": "") as “not provided”. In that case the code will fall back to CLI-detected betas and reintroduce the leakage this PR is trying to prevent. Consider tracking “client provided header” by key presence (case-insensitive) rather than value truthiness, so an empty value still suppresses CLI beta fallback.
| client_beta = headers.get("anthropic-beta") or headers.get("Anthropic-Beta") | |
| filtered_headers["anthropic-beta"] = self._merge_anthropic_beta(client_beta) | |
| client_provided_beta = bool(client_beta) | |
| client_beta_header_key = None | |
| for key in headers: | |
| if key.lower() == "anthropic-beta": | |
| client_beta_header_key = key | |
| if key == "anthropic-beta": | |
| break | |
| client_provided_beta = client_beta_header_key is not None | |
| client_beta = ( | |
| headers[client_beta_header_key] | |
| if client_beta_header_key is not None | |
| else None | |
| ) | |
| filtered_headers["anthropic-beta"] = self._merge_anthropic_beta(client_beta) |
Summary
When Claude Code CLI (or any external client) sends its own `anthropic-beta` header to the `/claude` endpoint, ccproxy was still merging in beta tags it had captured from the local `claude` CLI's own outbound requests during startup detection. Those CLI defaults — currently `context-1m-2025-08-07`, `advisor-tool-2026-03-01`, `effort-2025-11-24`, etc. — then ended up on every proxied request, including ones targeting models or accounts that don't support those betas.
Concretely, running:
```bash
ANTHROPIC_BASE_URL=http://127.0.0.1:8000/claude claude
```
…and triggering any small request against `claude-haiku-4-5-20251001` produced:
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "The long context beta is not yet available for this subscription."
}
}
```
…because haiku does not support the 1M-context beta at all, even on accounts that do have long-context access for Sonnet. The client never asked for `context-1m-2025-08-07` — ccproxy injected it.
Root cause
`ccproxy/plugins/claude_api/adapter.py` (line ~91 onward) does the merge in two stages:
PR #54/#56 ("merge client anthropic-beta tags with required OAuth tags") fixed step 1, but step 2 was a separate path that pre-dated it and kept smashing CLI-default betas into authoritative client requests. There was even a test asserting this broken behavior.
Fix
The client is authoritative for its own beta features. The CLI-detected `anthropic-beta` is now only used as a fallback when the client did not send one of its own. When the client provides any `anthropic-beta`, the outgoing request gets exactly:
```
client tags + claude-code-20250219 + oauth-2025-04-20
```
…and nothing else. Other CLI-detected headers (non-beta) still flow through unchanged.
Test plan
After this lands, `ANTHROPIC_BASE_URL=http://127.0.0.1:8000/claude claude` will stop hitting the long-context 400 on haiku requests, and accounts with mixed model access (Sonnet 1M + Haiku) will work correctly.