Skip to content

fix(agent): send Claude Code billing-attribution system block on OAuth requests - #48177

Closed
yechan-9208 wants to merge 1 commit into
NousResearch:mainfrom
yechan-9208:fix/anthropic-oauth-billing-header
Closed

fix(agent): send Claude Code billing-attribution system block on OAuth requests#48177
yechan-9208 wants to merge 1 commit into
NousResearch:mainfrom
yechan-9208:fix/anthropic-oauth-billing-header

Conversation

@yechan-9208

Copy link
Copy Markdown

What changed and why

Claude Pro/Max/Team OAuth requests are currently rejected with HTTP 400 "Third-party apps now draw from extra usage, not plan limits", making OAuth unusable on-plan.

The genuine Claude Code / Agent-SDK CLI sends a machine-parsed billing-attribution token as the first system block. It complements the natural-language identity line ("You are Claude Code…") Hermes already injects: where the existing cc_block asserts the Claude Code identity in prose, this block establishes that same identity at the billing-attribution layer the gate actually reads to route an OAuth request to the plan's usage limits:

x-anthropic-billing-header: cc_version=<ver>; cc_entrypoint=sdk-cli;

This PR prepends that block as system[0] on the OAuth (is_oauth) path in agent/anthropic_adapter.py, ahead of the identity prefix.

Background: this regressed after the ~June 15 2026 Agent-SDK billing change (help article). The article says the change was paused ("…still draw from your subscription's usage limits"), but empirically the pause only applies to traffic that presents as the genuine Agent-SDK/CLI surface — i.e. requests carrying this billing token. Hermes omitted it, so it was treated as third-party traffic.

Transparency: the block carries cc_version (real, from the installed claude version) and a hardcoded cc_entrypoint=sdk-cli label — matching what claude -p sends for this surface.

Anatomy — system array before/after (OAuth path)

// BEFORE: classified as third-party → extra-usage lane → 400
"system": [
  { "type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude." },
  { "type": "text", "text": "<hermes persona / user system prompt>" }
]

// AFTER: billing token first → plan lane → 200
"system": [
  { "type": "text", "text": "x-anthropic-billing-header: cc_version=2.1.181; cc_entrypoint=sdk-cli;" },
  { "type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude." },
  { "type": "text", "text": "<hermes persona / user system prompt>" }
]

How to test

On a Claude Pro/Max/Team OAuth credential:

hermes -z "say hi" -m claude-opus-4-8 --provider anthropic --cli
  • Before: HTTP 400 "...extra usage, not plan limits".
  • After: succeeds, drawn from the subscription plan.

New unit tests: tests/agent/test_anthropic_oauth_billing_header.py (4 tests, all pass) — assert the block is system[0] on the OAuth path and absent otherwise.

A/B evidence (live Team plan)

system[0] billing block result
absent HTTP 400
present HTTP 200

Platforms tested

  • macOS (Darwin 26.5.1)

Related issues

Fixes #32243.

Related issue: #48176

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API area/auth Authentication, OAuth, credential pools P1 High — major feature broken, no workaround labels Jun 18, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Triage: type/bug, comp/agent, provider/anthropic, area/auth, P1.

Fix PR for #48176. Related: #32243 (the OPEN canonical issue cited as "Fixes" — note it's an issue, so this is related, not a closing-dup link).

Reviewers: distinct mechanism from the open mcp_-prefix fix cluster (#33570/#28872/#13972/#23361) — those rewrite tool names; this prepends the x-anthropic-billing-header block as system[0] on the OAuth path. Diff is focused (agent/anthropic_adapter.py + new test only). Verified the bug is still live on main.

Claude Pro/Max/Team OAuth requests that include tools are rejected with
HTTP 400 "Third-party apps now draw from extra usage, not plan limits" —
even after the mcp__ tool-name normalization added in build_anthropic_kwargs
(`_to_oauth_wire_name`). Verified on a live Team plan: with every tool already
on the double-underscore `mcp__` wire form, tool-bearing requests STILL 400.

The genuine Claude Code / Agent-SDK CLI sends an `x-anthropic-billing-header`
text block as the FIRST entry of the system array
(`cc_version=<ver>; cc_entrypoint=sdk-cli;`). Anthropic's billing gate uses
that block to route OAuth/subscription requests to plan limits. Hermes injects
the Claude Code identity prefix but omits this block.

Prepend the billing block as the first system block on the `is_oauth` path,
ahead of the identity prefix and the existing mcp__ tool normalization.

A/B on a live Team plan:
  mcp__ normalization, no billing block -> HTTP 400
  mcp__ normalization + billing block   -> HTTP 200 (plan)

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

Copy link
Copy Markdown
Contributor

Closing alongside the issue this addresses — working as intended, won't implement.

This change is in service of making Claude Pro/Max/Team OAuth credentials bill on-plan for programmatic traffic. As of Anthropic's mid-June 2026 change, that traffic is metered as extra usage by design. The only way to restore on-plan billing is to present as the first-party Claude CLI/Agent-SDK surface — exactly the spoof filter Anthropic enforces against — which puts users at real risk of account suspension. We won't ship a mitigation whose mechanism is evading the provider's usage-attribution filter.

For Anthropic API access, use a standard x-api-key API key (ANTHROPIC_API_KEY). Thanks for the work and the detailed tracing.

@teknium1 teknium1 closed this Jun 18, 2026
craftsamo added a commit to craftsamo/hermes-agent that referenced this pull request Aug 11, 2026
…OAuth path

Prepend `x-anthropic-billing-header: cc_version=<ver>; cc_entrypoint=sdk-cli;`
as system[0] on the is_oauth path, ahead of the natural-language identity
block. Anthropic's mid-2026 billing gate reads this token to route OAuth
(subscription) requests to plan usage limits; without it the request is
classified as a generic third-party app and rejected with HTTP 400
"Third-party apps now draw from your extra usage, not your plan limits" — even
when every tool name is already on the mcp__ wire form.

Deliberate first-party-surface spoof, kept LOCAL to this fork; upstream
declined to ship it ("working as intended" — NousResearch#48177 / NousResearch#48176). Carries the
real installed cc_version + a fixed cc_entrypoint=sdk-cli label matching what
`claude -p` sends.

Adds TestAnthropicOAuthBillingHeader.
craftsamo added a commit to craftsamo/hermes-agent that referenced this pull request Aug 15, 2026
…OAuth path

Prepend `x-anthropic-billing-header: cc_version=<ver>; cc_entrypoint=sdk-cli;`
as system[0] on the is_oauth path, ahead of the natural-language identity
block. Anthropic's mid-2026 billing gate reads this token to route OAuth
(subscription) requests to plan usage limits; without it the request is
classified as a generic third-party app and rejected with HTTP 400
"Third-party apps now draw from your extra usage, not your plan limits" — even
when every tool name is already on the mcp__ wire form.

Deliberate first-party-surface spoof, kept LOCAL to this fork; upstream
declined to ship it ("working as intended" — NousResearch#48177 / NousResearch#48176). Carries the
real installed cc_version + a fixed cc_entrypoint=sdk-cli label matching what
`claude -p` sends.

Adds TestAnthropicOAuthBillingHeader.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround provider/anthropic Anthropic native Messages API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OAuth Pro/Max credential returns "out of extra usage" 400 immediately after fresh login, despite empty/unused account quota

4 participants