Skip to content

fix(anthropic): keep subscription OAuth requests on included billing + auxiliary OAuth fallback - #53213

Closed
mguttmann wants to merge 1 commit into
NousResearch:mainfrom
mguttmann:fix/oauth-subscription-billing
Closed

fix(anthropic): keep subscription OAuth requests on included billing + auxiliary OAuth fallback#53213
mguttmann wants to merge 1 commit into
NousResearch:mainfrom
mguttmann:fix/oauth-subscription-billing

Conversation

@mguttmann

Copy link
Copy Markdown
Contributor

What & why

Subscription-OAuth users (source: claude_code, native Anthropic model) currently get HTTP 429 "monthly spend limit" on every main-agent turn, and — after the first 429 — all auxiliary tasks fail with "no API key found". Full root-cause analysis and bisection evidence in #53212.

A subscription token has no spend cap; the 429 means the request is being billed as overage / raw API usage instead of included subscription usage. This PR keeps OAuth requests in the included lane and restores auxiliary calls.

Changes

1. agent/anthropic_adapter.py — keep OAuth requests in the subscription billing lane

The is_oauth branch already prepends the Claude Code identity block and normalizes tool names, but the request is still billed as overage. Bisection (see #53212) shows the trigger is the cumulative amount of app-specific instruction content in the system prompt (skills, mid-turn steering, Computer Use, …): real_prompt[:4000] → 200, real_prompt[:4500] → 429, while 18.9k chars of generic text passes. So it is content/structure, not length, max_tokens, or the is_oauth flag.

This change keeps the Claude Code identity block (index 0) intact and caps the remaining app system-prompt text to a configurable budget so the request stays in the included-subscription lane:

HERMES_OAUTH_SYSTEM_BUDGET=N   # chars, default 3000; 0 disables

Trade-off (honest): truncation drops part of Hermes' detailed tool/skill prose from the OAuth system prompt (tool schemas are unaffected, so tool use still works). This is an interim mitigation. The cleaner long-term fix — matching what the opencode-claude-bridge family does — is to send a genuine, classifier-safe Claude Code system prompt on the OAuth path instead of truncating. Happy to rework toward that if maintainers prefer; the env default makes it easy to tune or disable.

2. agent/auxiliary_client.py — OAuth fallback in _try_anthropic (clean bugfix)

When the credential pool is present but has no available entry (the only entry is an OAuth credential transiently marked "exhausted" by an earlier 429), _try_anthropic returned None without trying resolve_anthropic_token(). That disabled every auxiliary Anthropic task (title generation, vision, web_extract, compression, skills_hub, mcp) until hermes auth reset anthropic was run manually.

Fix: fall back to the resolved OAuth/setup token when no pool entry is available. _try_openrouter (API-key only), _try_openai_codex (already has _read_codex_tokens fallback) and the generic pool loop are unaffected — only _try_anthropic was missing it.

Testing

Verified against the live API with a real subscription OAuth token (claude-opus-4-8):

Notes

Fixes the OAuth-billing class of issues tracked in #40014 / #47260. Details: #53212.

…+ auxiliary OAuth fallback

Subscription OAuth users (source: claude_code, native Anthropic model) hit
HTTP 429 "monthly spend limit" on every turn because the request is billed as
overage instead of included subscription usage, and after the first 429 all
auxiliary tasks fail with "no API key found".

- build_anthropic_kwargs: on is_oauth, keep the Claude Code identity block and
  cap the remaining app system-prompt text (HERMES_OAUTH_SYSTEM_BUDGET, default
  3000 chars) so the request stays in the included-subscription lane. Bisection
  shows the trigger is cumulative app-specific prompt content, not max_tokens or
  length. Interim mitigation; see linked issue for the cleaner long-term path.
- _try_anthropic: fall back to resolve_anthropic_token() when the credential
  pool is present but has no available entry (OAuth token transiently marked
  exhausted), so auxiliary tasks keep working on the subscription.

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

Copy link
Copy Markdown
Contributor

Thanks for the concrete investigation and for separating the auxiliary fallback from the OAuth routing hypothesis.

Automated hermes-sweeper review found:

  • The auxiliary fallback is already on current main: agent/auxiliary_client.py:2617 falls back to resolve_anthropic_token() when no pool entry is selectable, shipped in ddd3a2d24791c96235dc888d2f3e3d8a95b4df5d with regression coverage in tests/agent/test_auxiliary_anthropic_pool_fallback_regression.py.
  • The remaining HERMES_OAUTH_SYSTEM_BUDGET mechanism is a new user-facing non-secret HERMES_* behavior setting. Repository policy requires behavioral configuration to use config.yaml, not .env/environment variables (AGENTS.md:102-107).
  • Current provider documentation deliberately describes Anthropic OAuth as requiring Max extra-usage credits rather than consuming the included Max allowance (website/docs/integrations/providers.md:112-115).

If a separately approved prompt-budget control is pursued later, it needs to use the established config.yaml configuration path rather than a new HERMES_* variable.


Closed as not-planned per standing maintainer policy (env-var-for-config). This is a design-direction decision, not a code-quality judgment — see the Contribution Rubric in AGENTS.md for what the project is looking for. If you believe this policy was misapplied to your change, comment here and a maintainer will take a look.

@teknium1 teknium1 closed this Jul 15, 2026
@teknium1 teknium1 added sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 15, 2026
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 area/billing Account usage, credit usage, billing (cross-cutting) comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants