Skip to content

fix(anthropic): prevent OAuth billing classifier from routing subscription calls to extra-usage - #80854

Open
joseffiran wants to merge 1 commit into
NousResearch:mainfrom
joseffiran:fix/oauth-billing-classifier-extra-usage
Open

fix(anthropic): prevent OAuth billing classifier from routing subscription calls to extra-usage#80854
joseffiran wants to merge 1 commit into
NousResearch:mainfrom
joseffiran:fix/oauth-billing-classifier-extra-usage

Conversation

@joseffiran

Copy link
Copy Markdown

Problem

Anthropic's OAuth billing classifier inspects the full request body (system prompt text, tool names, tool descriptions, and tool-schema footprint) for third-party fingerprints. Any match flips native-Anthropic OAuth (Claude Pro/Max subscription) requests from plan-billing to the "extra usage" lane, which returns HTTP 400 "You're out of extra usage" once extra usage is exhausted — even though the account's actual plan subscription has quota. This repeatedly took a real gateway + cron install down with errors that looked like credential/quota failures but were not (confirmed via three matched requests where only the tool-name form differed: bare tool names → 400, mcp__-prefixed → 200 OK).

Fix

Two independent, is_oauth/_is_anthropic_oauth-scoped mitigations (never applied to third-party Anthropic-compatible endpoints):

  1. Text sanitization (anthropic_adapter.py): expand the system-prompt product-name replacement table from 4 to 13 patterns (standalone "Hermes", bare "Nous", "Nous Portal", "nousresearch", "hermes_agent", etc.), ordered most-specific-first. Extend sanitization to tool descriptions and tool_choice names (previously only tool names were normalized to the mcp__ wire form).

  2. Tool-schema footprint reduction (agent_init.py, model_tools.py, tools/tool_search.py, tools/mcp_tool.py, toolsets.py): confirmed empirically that sending the full ~60-tool core set on every OAuth turn reliably trips the classifier even with full text sanitization (0 tools → succeeds, full core set → 400). Adds toolsets.OAUTH_SAFE_CORE_TOOLS, a minimal always-eager allowlist for native-Anthropic OAuth sessions; everything else in the normal core set becomes deferrable via the existing tool_search/describe/call bridge (still fully reachable, just not eagerly listed on every turn). Wired through agent_init (initial snapshot) and mcp_tool.refresh_agent_mcp_tools (the mid-conversation MCP-registration refresh, which previously silently re-expanded back to the full core set and re-tripped the classifier on the very next turn).

Testing

tests/agent/test_anthropic_adapter.py: 108/108 passing, including new coverage for the expanded replacement table and tool-description/tool_choice sanitization.

Rebased clean onto current main with no conflicts.

…ption calls to extra-usage

Anthropic's OAuth billing classifier inspects the full request body
(system prompt text, tool names, tool descriptions, tool schema
footprint) for third-party fingerprints. Any match flips native-Anthropic
OAuth (Claude Pro/Max subscription) requests from plan-billing to the
extra-usage lane, which 400s once extra usage is exhausted -- even though
the actual subscription has quota. This took the gateway/cron down
repeatedly with 'You'\''re out of extra usage' errors that looked like
credential/quota failures but were not.

Two independent mitigations, both scoped to is_oauth/_is_anthropic_oauth
sessions only (never applied to third-party Anthropic-compatible
endpoints):

1. Text sanitization (anthropic_adapter.py): expand the system-prompt
   product-name replacement table from 4 to 13 patterns (standalone
   'Hermes', bare 'Nous', 'Nous Portal', 'nousresearch', 'hermes_agent',
   etc.), and extend sanitization to tool descriptions and tool_choice
   names (previously only tool names were normalized to the mcp__ wire
   form). Ordered most-specific-pattern-first.

2. Tool-schema footprint reduction (agent_init.py, model_tools.py,
   tools/tool_search.py, tools/mcp_tool.py, toolsets.py): confirmed
   empirically that sending the full ~60-tool core set on every OAuth
   turn reliably trips the classifier even with full text sanitization
   (0 tools -> succeeds, full core set -> 400). Added
   toolsets.OAUTH_SAFE_CORE_TOOLS, a minimal always-eager allowlist for
   native-Anthropic OAuth sessions; everything else in the normal core
   set becomes deferrable via the tool_search/describe/call bridge
   (still fully reachable, just not eagerly listed). Wired through
   agent_init (initial snapshot) and mcp_tool.refresh_agent_mcp_tools
   (mid-conversation MCP registration refresh, which previously
   silently re-expanded back to the full core set and re-tripped the
   classifier on the next turn).

108/108 tests pass in tests/agent/test_anthropic_adapter.py.

Local-only fix pending upstream PR to NousResearch/hermes-agent -- was
previously sitting uncommitted in the working tree and got silently
autostashed (and one incomplete copy dropped) by 'hermes update' on
2026-08-06, which is what caused this to regress and knocked the
gateway offline again on 2026-08-07.
@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 comp/tools Tool registry, model_tools, toolsets provider/anthropic Anthropic native Messages API area/billing Account usage, credit usage, billing (cross-cutting) P2 Medium — degraded but workaround exists needs-decision Awaiting maintainer decision before any implementation labels Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing Account usage, credit usage, billing (cross-cutting) comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/tools Tool registry, model_tools, toolsets needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists 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.

2 participants