Skip to content

fix(gateway): filter anthropic-beta on the Vertex Anthropic path + drop cch sign (#3358)#3375

Open
StarryKira wants to merge 4 commits into
Wei-Shaw:mainfrom
StarryKira:fix/3358-vertex-beta-and-cch
Open

fix(gateway): filter anthropic-beta on the Vertex Anthropic path + drop cch sign (#3358)#3375
StarryKira wants to merge 4 commits into
Wei-Shaw:mainfrom
StarryKira:fix/3358-vertex-beta-and-cch

Conversation

@StarryKira

Copy link
Copy Markdown
Contributor

Summary

Fixes #3358. Recent Claude Code CLI versions changed their request shape, which broke two independent gateway paths:

  1. New anthropic-beta tokens break Vertex. Vertex AI's Anthropic endpoint returns HTTP 400 for unknown beta tokens (advisor-tool-2026-03-01, prompt-caching-scope-2026-01-05, redact-thinking-2026-02-12, thinking-token-count-2026-05-13). buildUpstreamRequestAnthropicVertex forwarded the client anthropic-beta header verbatim via the allowedHeaders whitelist — it was the only upstream builder with no beta filtering (the OAuth/API-key path uses computeFinalAnthropicBeta; Bedrock uses filterBedrockBetaTokens). This is the same gap that the closed PR fix(gateway): apply BetaPolicy filter / block on the Vertex Anthropic path #3150 identified.
  2. The CLI canceled the cch sign. Newer CLIs no longer emit the cch=… signature field in their x-anthropic-billing-header system block. sub2api still injected cch=00000; (and optionally signed it) when mimicking Claude Code for OAuth accounts, so mimicked requests now diverge from real CLI traffic.

Changes (4 focused commits)

  1. fix(gateway): filter anthropic-beta on the Vertex Anthropic path — adds a vertexSupportedBetaTokens whitelist (mirrors bedrockSupportedBetaTokens) + filterVertexBetaTokens. In buildUpstreamRequestAnthropicVertex: runs the BetaPolicy block check (symmetric to resolveBedrockBetaTokensForRequest), strips policy-filtered + non-whitelisted tokens, drives the body context_management sanitize off the final beta, and overwrites the anthropic-beta header after the whitelist copy loop.
  2. test(gateway): Vertex anthropic-beta filtering — reproduces the prod 400 (the four rejected tokens stripped, whitelisted ones kept), header drop when all stripped, body sanitize keyed on final beta, BetaPolicy block returns BetaBlockedError, plus helper unit tests.
  3. fix(claude-mimicry): drop the cch sign to match new Claude Code CLIbuildBillingAttributionText no longer emits cch=00000; (keeps cc_version + cc_entrypoint=cli, which detection relies on); retires the now-dead signBillingHeaderCCH/xxHash64Seeded helpers and both call sites; enable_cch_signing becomes a documented no-op.
  4. test(claude-code): detection recognizes the new-CLI billing block (no cch) — locks in that detection keys on cc_entrypoint=cli, not cch, and that dropping cch did not loosen the UA gate (ClaudeCodeOnly groups can't be spoofed).

Notes

  • Vertex whitelist membership is the one empirical unknown; seeded conservatively (context-1m, context-management, fine-grained-tool-streaming, interleaved-thinking) and structured so tokens are trivial to add. Excludes the four 400-causing tokens and the identity betas (claude-code-20250219 / oauth-2025-04-20) that Vertex service_account doesn't need.
  • "cch canceled" is read literally as the cch= field only — the billing block and cc_entrypoint=cli are retained.
  • Commits 1+2 (the reported Vertex Anthropic service_account forwards unsupported anthropic-beta headers from Claude Code #3358 bug) are independent of 3+4 and can ship on their own.

Test

cd backend
go build ./...
go test -tags=unit ./internal/service/...   # all green
golangci-lint run ./internal/service/...     # 0 issues

🤖 Generated with Claude Code

StarryKira and others added 4 commits June 19, 2026 07:51
…-Shaw#3358)

Vertex AI's Anthropic endpoint rejects unknown anthropic-beta tokens with
HTTP 400. buildUpstreamRequestAnthropicVertex forwarded the client header
verbatim via the allowedHeaders whitelist, so recent Claude Code CLIs that
send advisor-tool-2026-03-01, prompt-caching-scope-2026-01-05,
redact-thinking-2026-02-12 and thinking-token-count-2026-05-13 broke every
Vertex service_account request, even though plain account-test requests passed.

This is the only upstream builder that bypassed beta filtering: the
OAuth/API-key path uses computeFinalAnthropicBeta and the Bedrock path uses
filterBedrockBetaTokens. Close the gap with a Vertex-specific whitelist
(vertexSupportedBetaTokens) mirroring bedrockSupportedBetaTokens, plus the
existing BetaPolicy block check:

- evaluateBetaPolicy block check (symmetric to resolveBedrockBetaTokensForRequest)
- filterVertexBetaTokens strips policy-filtered + non-whitelisted tokens
- body context_management sanitize now keys on the final beta, not the raw client value
- overwrite the anthropic-beta header after the whitelist copy loop with the final value

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the Wei-Shaw#3358 fix:
- StripsUnsupportedClaudeCodeTokens reproduces the prod 400 — the four Vertex-
  rejected tokens (advisor-tool, prompt-caching-scope, redact-thinking,
  thinking-token-count) plus the identity betas are stripped while whitelisted
  tokens survive. Fails before the builder fix, passes after.
- DropsHeaderWhenAllUnsupported: no anthropic-beta header is sent when every
  client token is filtered out.
- BodySanitizeKeysOnFinalBeta: body.context_management is stripped based on the
  final beta, not the raw client value.
- BlocksViaBetaPolicy: an admin block rule on a Vertex account returns BetaBlockedError.
- TestFilterVertexBetaTokens unit-tests whitelist/drop-set/dedupe/empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recent Claude Code CLI versions no longer emit the cch=... signature field in
their x-anthropic-billing-header system block (issue Wei-Shaw#3358). sub2api still
injected cch=00000 when mimicking Claude Code for OAuth accounts and optionally
signed it, so mimicked requests now diverge from real CLI traffic — the opposite
of what the mimicry is for.

- buildBillingAttributionText emits the block without the cch=00000 segment;
  cc_version + cc_entrypoint=cli are kept (detection and Anthropic's first-party
  signal rely on the block, not on cch).
- Retire signing: remove the two enableCCH signBillingHeaderCCH call sites in
  buildUpstreamRequest / buildCountTokensRequest and delete the now-dead
  signBillingHeaderCCH, cchPlaceholderRe, cchSeed, xxHash64Seeded helpers.
- enable_cch_signing is now a documented no-op (kept for backward compat).
- Drop the obsolete signing tests (TestSignBillingHeaderCCH, TestXXHash64Seeded,
  TestSanitizeMustBeBeforeCCHSigning_HashConsistency) and update the prompt test
  to assert the injected block no longer carries cch=.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… cch)

Locks in that Claude Code detection keys on the billing block prefix +
cc_entrypoint=cli, not on the cch field that the new CLI (and now our own
mimicry) no longer sends:

- BillingBlockRecognizedWithoutCCH: an identity-prose-less sub-request whose
  system block is `x-anthropic-billing-header: cc_version=...; cc_entrypoint=cli;`
  (no cch) is still detected as Claude Code.
- NoCCHBlockStillRequiresClaudeCodeUA: dropping cch did not loosen detection —
  a non-claude-cli UA is still rejected, so ClaudeCodeOnly groups can't be spoofed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 19, 2026 15:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Vertex Anthropic service_account forwards unsupported anthropic-beta headers from Claude Code

2 participants