feat(oauth): HERMES_OAUTH_* env flags to bypass Claude.ai content-filter triggers - #20865
feat(oauth): HERMES_OAUTH_* env flags to bypass Claude.ai content-filter triggers#20865masserfx wants to merge 3 commits into
Conversation
…iggers Anthropic's Claude.ai OAuth subscriptions enforce three undocumented content-filter rules that cause Hermes-on-OAuth deployments (gateway, cron, etc.) to fail with a misleading HTTP 400 "out of extra usage" on every request that carries non-trivial system prompts or tools. This PR introduces three env-var gates so an OAuth deployment can opt out of the offending behaviours without changing default behaviour for existing API-key or Claude-Code-CLI deployments. ## Triggers identified (via empirical bisection) 1. **`mcp_*` tool name prefix.** Hermes mimics the Claude Code CLI convention by prefixing every tool with `mcp_`. The filter rejects `mcp_*` names that aren't registered with the account's Claude Code MCP setup. Renamed tools (no prefix) pass. 2. **SKILLS_GUIDANCE + MEMORY_GUIDANCE combined.** Either guidance block alone passes; injecting both together trips the filter. Skipping SKILLS_GUIDANCE while keeping MEMORY_GUIDANCE preserves most of the useful behaviour. 3. **`context-1m-2025-08-07` beta on subscriptions without 1M entitlement.** Already handled reactively in `run_agent.py` (drop + retry), but auxiliary clients (`title_generator`, summarization, etc.) build their Anthropic client without `drop_context_1m_beta=True` and fail immediately with "long context beta is not yet available" — no retry path. The new env flag makes the strip universal across every `build_anthropic_client` caller. ## Env flags (all default off — zero behaviour change for existing users) | Env var | Effect | Affects | |---|---|---| | `HERMES_OAUTH_NO_MCP_PREFIX=1` | Skip mcp_ tool prefix on OAuth | `_oauth_mcp_prefix_enabled()` in adapter | | `HERMES_OAUTH_COMPACT_GUIDANCE=1` | Skip SKILLS_GUIDANCE injection | `tool_guidance` build in run_agent | | `HERMES_OAUTH_FORCE_DROP_1M_BETA=1` | Universal context-1m strip | `_common_betas_for_base_url` | Recommended bundle for Claude.ai OAuth deployments: enable all three. ## Verification Telegram gateway running 24/7 on `claude-haiku-4-5` via Claude.ai subscription failed every message with HTTP 400 "out of extra usage" — identical session post-fix returns: user: Test po fixu assistant: Super! 👍 Vypadá to, že je všechno v pořádku. ## Test plan - [x] `test_force_drop_1m_beta_via_env` — env var strips context-1m - [x] `test_force_drop_1m_beta_default_off` — default behaviour preserved - [x] `test_oauth_no_mcp_prefix_env_skips_tool_renaming` — env var disables prefix - [x] `test_oauth_mcp_prefix_default_on` — default behaviour preserved - [x] Existing `test_oauth_drop_context_1m_beta_strips_only_1m` still passes - [x] All 6 tests pass under pytest-xdist ``` $ venv/bin/python -m pytest tests/agent/test_anthropic_adapter.py -k "drop_context_1m or force_drop or mcp_prefix or no_mcp" -v 6 passed in 2.30s ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both scripts have been live on the Hetzner host for a while but never versioned, so they could (and did) drift from what the gateway expects. - hermes-agent-updater.sh — cron-driven auto-updater. Now tracks the masserfx fork branch (not NousResearch/main, which lacks the OAuth content-filter workarounds the gateway depends on) and syncs deps via `uv sync` rather than `pip install`, since the venv is uv-managed and has no pip/ensurepip inside. - hermes-agent-warmup.py — post-restart health probe that exercises the patched OAuth path with a minimal messages.create call. Import of `_oauth_mcp_prefix_enabled` is now wrapped in try/except so the script degrades gracefully on checkouts that don't have the symbol yet. Paths and remote names are hard-coded for the leos@hetzner deployment; adapt before reusing on another box.
…d + paperclip report fix Two production fixes from a server health-check, archived as source-of-truth under scripts/ops/ (manual-deploy, not auto-deployed): - systemd/hermes-agent.service: add ExecStartPre DNS readiness guard. network-online.target does not guarantee a working resolver, causing a boot race where the gateway failed to resolve api.telegram.org, got stuck on a sticky fallback IP and leaked a dead socket. Guard waits up to 60s for DNS (visible log, no silent fallback) then starts anyway. - paperclip/daily_report.py: control plane moved to localhost:3100 (public paperclip.frigeble.com:443 was taken over by the Plane PM proxy). Read base URL from PAPERCLIP_API_URL; board credentials now come solely from .env with fail-fast (removed hardcoded password default — was a plaintext secret). - paperclip/run_daily_report.sh: source BOARD_EMAIL/BOARD_PASSWORD from .env. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for documenting the OAuth symptoms and supplying focused adapter tests. This automated hermes-sweeper review is closing this because the proposed user-facing mechanism conflicts with a standing configuration policy.
If a remaining user-configurable behavior is needed after the current OAuth path is evaluated, it should be proposed through the documented Closed as not-planned per standing maintainer policy ( |
Context
Anthropic's Claude.ai OAuth subscriptions enforce three undocumented content-filter rules that surface as a misleading HTTP 400
"out of extra usage"on Hermes deployments. This PR ships three opt-in env-var gates so OAuth deployments can disable the offending behaviours. All default off — zero behaviour change for API-key or Claude-Code-CLI users.Companion to #20850 (recognises
"out of extra usage"as the second variant of the existingoauth_long_context_beta_forbiddenclassifier). That PR enables reactive recovery for variant (2); this PR addresses the underlying triggers so the strip is permanent and the auxiliary-client path also works.The three triggers (empirically bisected)
mcp_tool-name prefix on tools not registered with the account's Claude Code MCP setupHERMES_OAUTH_NO_MCP_PREFIX=1SKILLS_GUIDANCE+MEMORY_GUIDANCEinjected togetherHERMES_OAUTH_COMPACT_GUIDANCE=1SKILLS_GUIDANCE;MEMORY_GUIDANCEretainedcontext-1m-2025-08-07beta on subscriptions without 1M entitlement, on auxiliary clients (title_generator, summarization)HERMES_OAUTH_FORCE_DROP_1M_BETA=1_common_betas_for_base_urlEnabling all three is the recommended bundle for Claude.ai OAuth deployments.
Why each is needed
(1)
mcp_prefix. Today's behaviour addsmcp_to every tool name whenis_oauth=True(anthropic_adapter.py:1862). The filter rejects names not registered with the account's Claude Code MCP setup. Bisection: identical request with prefix → HTTP 400; without prefix → HTTP 200.(2) Combined guidance blocks. Either alone passes; together they trip the filter. Bisection of
MEMORY_GUIDANCE alone (1371c) → OK,SKILLS_GUIDANCE alone (385c) → OK,MEMORY + SKILLS only (1757c) → FAIL. TheMEMORY_GUIDANCEis more useful in practice (cross-session preferences); droppingSKILLS_GUIDANCEis the lower-impact strip.(3) Universal
context-1mstrip. The reactive recovery inrun_agent.py:12232flips_oauth_1m_beta_disabledon subscriptions that reject the beta — but only for the main runtime client. Auxiliary clients (agent/auxiliary_client.pybuilds them in 4+ spots) callbuild_anthropic_client(...)withoutdrop_context_1m_beta=True, sotitle_generatorand summarization fail every time on Claude.ai OAuth. Reading the env var inside_common_betas_for_base_urlmakes the strip universal without each call site having to thread the parameter.Verification
Telegram gateway running 24/7 on
claude-haiku-4-5via a Claude.ai subscription:Before: 120× HTTP 400 / day, every cron and every Telegram message dies.
After (all 3 flags + companion #20850):
Test plan
test_force_drop_1m_beta_via_env— env var stripscontext-1m-2025-08-07test_force_drop_1m_beta_default_off— default behaviour preservedtest_oauth_no_mcp_prefix_env_skips_tool_renaming— env var skips prefixtest_oauth_mcp_prefix_default_on— defaultmcp_*prefix preservedtest_oauth_drop_context_1m_beta_strips_only_1m(existing) still passes🤖 Generated with Claude Code