Claude Code OAuth bypass for hermes-agent, use your Claude Code subscription (Max/Pro) with Hermes.
Patches hermes-agent at runtime to pass Anthropic's server-side OAuth content validation. It does not modify hermes-agent source files. Installation happens through a Python import hook that monkey-patches build_anthropic_kwargs on startup.
On 2026-04-04, Anthropic added server-side validation that rejects OAuth requests from third-party tools. This patch adds the billing header signature and system prompt structure the API expects.
- hermes-agent installed (
$HERMES_HOME/hermes-agent/, defaults to~/.hermes/hermes-agent/on Linux/macOS and%LOCALAPPDATA%\hermes\hermes-agent\on Windows) - Claude Code CLI authenticated (valid credentials at
~/.claude/.credentials.json) - hermes-agent configured for OAuth (
credential_poolhas aclaude_codeentry in$HERMES_HOME/auth.json) - Python 3.11+
- Windows: PowerShell 5.1+ and Git in PATH
# Remote install (one-liner)
curl -fsSL https://raw.githubusercontent.com/kristianvast/hermes-claude-auth/main/install-remote.sh | bash
# Or clone manually
git clone https://github.com/kristianvast/hermes-claude-auth.git
cd hermes-claude-auth
./install.shirm https://raw.githubusercontent.com/kristianvast/hermes-claude-auth/main/install-remote.ps1 | iexOr clone manually:
git clone https://github.com/kristianvast/hermes-claude-auth.git
cd hermes-claude-auth
.\install.ps1What the installer does:
- Auto-detects your hermes directory (
$HERMES_HOMEif set, else%LOCALAPPDATA%\hermes\on Windows and~/.hermes/on Linux/macOS) - Copies
anthropic_billing_bypass.pyto<hermes-dir>/patches/ - Installs a
.pthshim + bootstrap module into the hermes venv's site-packages (this loads the hook at interpreter startup; see "How it works" below for why a.pthand notsitecustomize.py) - Mirrors Claude Code credentials from the OS credential store to
~/.claude/.credentials.json(macOS Keychain / Windows Credential Manager) - Restarts
hermes-gateway.serviceif running (Linux only)
Use this prompt when another Hermes instance needs to install, repair, or audit Claude auth without losing the Antigravity provider:
You are setting up hermes-claude-auth on this machine.
Work from the persistent clone at ~/hermes-claude-auth. If it exists and is
clean, run `git pull`; if it has local changes, do not overwrite them and
report that pull was skipped. If it does not exist, run:
`git clone https://github.com/kristianvast/hermes-claude-auth.git ~/hermes-claude-auth`
Then run:
`cd ~/hermes-claude-auth && ./install.sh`
After install, verify and report:
- `./install.sh --check` passes.
- `~/.hermes/patches/anthropic_billing_bypass.py` matches this repo.
- The Hermes venv `sitecustomize.py` contains `# hermes-claude-auth managed`.
- If `# hermes-antigravity managed` is also present, confirm the shared hook
still contains Antigravity hooks for `hermes_cli.auth`,
`hermes_cli.providers`, `hermes_cli.commands`, `cli`,
`agent.auxiliary_client`, `hermes_cli.runtime_provider`,
`hermes_cli.main`, `hermes_cli.model_switch`, and `api.config`.
- Importing `agent.error_classifier` in the Hermes venv does not print a
`ModuleNotFoundError` traceback for `anthropic_billing_bypass`.
- `~/.hermes/hermes-agent/.git/hooks/post-merge` exists and is executable.
If Claude credentials are available, smoke-test:
`hermes chat --provider anthropic -m claude-sonnet-4-6 -q "OK" -Q`
If Antigravity is also installed and credentials are available, smoke-test:
`hermes chat --provider google-antigravity -m gemini-3.5-flash-high -q "OK" -Q`
If any smoke test cannot be run, say exactly what blocked it. Finish with the
current git commit, whether automatic repair is installed, and the check/smoke
results.
./uninstall.sh # remove hook only
./uninstall.sh --purge # remove hook + patch file.\uninstall.ps1 # remove hook only
.\uninstall.ps1 -Purge # remove hook + patch filehermes update can wipe the loader from the venv's site-packages/ in two
ways:
git merge/git checkoutof the hermes-agent repo — triggers git hooks, but the default.git/hooks/dir is inside the repo and gets stashed by the update'sgit stashstep, so hooks there don't fire at the right time.- Venv rebuild — the update provisions a fresh Python runtime, which
deletes
sitecustomize.pyoutright. No git hook covers this.
Layer 1 — git hooks via core.hooksPath (outside the repo):
# From inside your hermes-agent checkout:
mkdir -p "$LOCALAPPDATA/hermes/git-hooks"
cp post-merge.hook.sh "$LOCALAPPDATA/hermes/git-hooks/post-merge"
cp post-checkout.hook.sh "$LOCALAPPDATA/hermes/git-hooks/post-checkout"
chmod +x "$LOCALAPPDATA/hermes/git-hooks/post-"*
git config core.hooksPath "$LOCALAPPDATA/hermes/git-hooks"Because the hooks live outside .git/hooks/, the update's git stash
can't touch them, and core.hooksPath makes git use them for every
merge/checkout.
Layer 2 — watchdog cron (covers venv rebuild):
# Hermes cron runs restore_loader.sh every 15m; it re-copies the loader
# from $LOCALAPPDATA/hermes/patches/sitecustomize.py if it's missing.
hermes cron create "every 15m" --name restore-claude-auth-loader \
--no-agent --script restore_loader.sh --deliver localThe cron job only fires while the Hermes gateway is running — after an update, the loader is restored within ≤15 minutes of the gateway coming back up.
Both layers are idempotent and never break the update. The canonical loader
lives at $LOCALAPPDATA/hermes/patches/sitecustomize.py (outside any repo).
Windows note: hooks are POSIX shell scripts run under Git Bash (ships with Git for Windows). The cron script uses
unameto resolve paths and works under the Hermes gateway's environment.
- Billing header: SHA-256 signed
x-anthropic-billing-headerinjected assystem[0] - System prompt relocation: Non-identity system entries moved to the first user message as
<system-reminder>blocks - Beta flags: Adds
prompt-caching-scope-2026-01-05andadvisor-tool-2026-03-01 - Stainless SDK spoof: Lowercase
x-stainless-*headers +anthropic-dangerous-direct-browser-access+?beta=truequery param matching real Claude Code 2.1.112 - Tool name namespacing: Hermes's
mcp_bashis rewritten tomcp__hermes__Bashoutbound; the response normalizer unwraps it back tobashso hermes's tool dispatcher resolves the registered name without auto-repair noise - Tool pair repair: Orphaned
tool_use/tool_resultblocks (left by long conversations or partial summaries) are stripped before signing — prevents HTTP 400 (upstream PR #136) - Haiku effort stripping:
effortparameter is removed for haiku models that reject it with HTTP 400 (upstream PR #126) - Temperature fix: Strips non-default
temperatureon Opus 4.6 adaptive thinking, which otherwise rejects with HTTP 400 - Account metadata: Maps
~/.claude.json::oauthAccount.accountUuidtometadata.user_id(Anthropic rejected the olderaccount_uuidkey with HTTP 400 on 2026-04-29) - Window-aware rate-limit auto-wait (v1.5.9+): when Anthropic returns HTTP 429 mid-agent, reads the
anthropic-ratelimit-unified-{5h,1d,7d}-status/-resetheaders to identify which subscription window tripped, picks the longest waiting window (sleeping through a 7d reset also clears 5h), and applies a per-window safety cap (HERMES_RL_AUTOWAIT_MAX_5H_S=6h,_1D_S=26h,_7D_S=7.5d). The legacy 6h cap is kept as the fallback when no window-specific status is present. Per-window caps can be tuned via env vars.
Installed through a .pth file in the venv's site-packages that imports a small bootstrap module at interpreter startup, which in turn registers a MetaPathFinder hook for agent.anthropic_adapter. No source modifications.
The .pth shim runs before site.py imports sitecustomize, on every platform. An earlier version of this installer wrote a sitecustomize.py into site-packages directly, which failed silently on Debian/Ubuntu — those distros ship /usr/lib/pythonX.Y/sitecustomize.py for apport and it wins import priority over the venv-local one, so the bypass hook never ran. The current installer auto-migrates legacy installs.
When a long agent run exhausts a Claude Pro/Max usage window mid-flight,
api.anthropic.com returns HTTP 429 with a reset time in the
anthropic-ratelimit-unified-*-reset headers. Hermes core's retry loop
caps backoff at 120s and abandons the run, surfacing "rate-limiting
requests" on Telegram and killing the session.
This patch wraps the two API-call entry points on run_agent.AIAgent so a
genuine subscription-window 429 instead sleeps until the window resets
(interruptibly, in short chunks) and then retries the same call transparently.
It picks the longest throttled window (e.g. a 7d hit also clears the 5h
window, so a single sleep covers both) and applies a per-window safety
cap (5h=6h, 1d=26h, 7d=7.5d). Behaviour is tunable via env vars:
| Env var | Default | Meaning |
|---|---|---|
HERMES_RL_AUTOWAIT |
1 |
0 disables auto-wait entirely |
HERMES_RL_AUTOWAIT_MAX_S |
21600 |
fallback cap (s) when no window detected |
HERMES_RL_AUTOWAIT_MAX_5H_S |
21600 |
5h-window safety cap (6h) |
HERMES_RL_AUTOWAIT_MAX_1D_S |
93600 |
1d-window safety cap (26h) |
HERMES_RL_AUTOWAIT_MAX_7D_S |
648000 |
7d-window safety cap (7.5d) |
HERMES_RL_AUTOWAIT_BUFFER_S |
5 |
pad added after reset |
HERMES_RL_AUTOWAIT_DEFAULT_S |
300 |
wait when no reset header found |
The wait loop also calls agent._touch_activity(...) every ~25s so the
gateway's inactivity watchdog doesn't kill the agent mid-wait. Idempotent
and never breaks the billing path: if anything goes wrong, the original
call/exception behaviour is preserved.
Ported from kristianvast/hermes-claude-auth PR #27 (window-aware auto-wait); the fingerprint parity fix below from PR #21.
Anthropic's validator cross-references the user-agent header and the
cc_version= field in the billing header. If the SDK client sends
claude-cli/<ver> (external, cli) while the billing header claims
cc_entrypoint=sdk-cli, the request is flagged as third-party and routed to
pay-per-token extra usage instead of your Max/Pro plan
(HTTP 400 You're out of extra usage). This patch pins the Claude Code
version to 2.1.112 for both the user-agent and the signed billing
header, and forces x-app: cli, eliminating the drift that triggered
upstream issue #6.
| File | Action |
|---|---|
$HERMES_HOME/patches/anthropic_billing_bypass.py |
Created |
<venv>/lib/pythonX.Y/site-packages/hermes_claude_auth.pth (Linux/macOS)<venv>\Lib\site-packages\hermes_claude_auth.pth (Windows) |
Created |
<venv>/lib/pythonX.Y/site-packages/_hermes_claude_auth_bootstrap.py (Linux/macOS)<venv>\Lib\site-packages\_hermes_claude_auth_bootstrap.py (Windows) |
Created |
<venv>/lib/pythonX.Y/site-packages/sitecustomize.py |
Removed if left behind by a legacy install (original restored from .pre-hermes-claude-auth backup when present) |
| hermes-agent source files | NOT modified |
When you run hermes update (which does git pull + pip install), the
sitecustomize.py inside the venv may be overwritten. The patch file survives.
Automatic recovery (default). install.sh installs a git post-merge hook
into ~/.hermes/hermes-agent/.git/hooks/, so the moment hermes update runs its
git pull, the hook detects the missing hook and re-runs recovery automatically.
If the Google Antigravity plugin is also installed, its coexistence
sitecustomize.py (which already contains the Claude hook) is restored first and
this installer leaves it untouched — the two patches never clobber each other.
Keep the clone in a persistent path (e.g.
~/hermes-claude-auth), not/tmp. The post-merge hook looks for the installer at$HOME/hermes-claude-auth/install.shfirst; a/tmpclone is wiped on reboot and auto-recovery silently can't run.
Check what's broken (verifies files exist AND match the repo byte-for-byte):
cd ~/hermes-claude-auth
./install.sh --check--check flags content drift too — if the installed
anthropic_billing_bypass.py differs from the repo (e.g. a hot-fix was applied
to one but not synced to the other), it reports [!] DRIFT so you can sync the
newer copy back before a clean install silently reverts it. The shared
sitecustomize.py is intentionally not compared (it legitimately differs when
the Antigravity plugin's multi-hook version is installed).
Recover (only restores sitecustomize.py + patch):
cd ~/hermes-claude-auth
git pull && ./install.sh --post-updateFull recovery:
cd ~/hermes-claude-auth
git pull && ./install.sh| File | Location | Survives? |
|---|---|---|
anthropic_billing_bypass.py |
~/.hermes/patches/ |
✅ Outside repo |
sitecustomize.py |
venv site-packages/ |
❌ Overwritten |
| Claude credentials | ~/.claude/ |
✅ Managed by Claude CLI |
| Auth token | ~/.hermes/auth.json |
✅ Outside repo |
Only sitecustomize.py needs recovery. --post-update does exactly that.
- Tested with hermes-agent on Python 3.11+
- Linux, macOS, and Windows (native:
%LOCALAPPDATA%\hermes; the bypass patch lives at%LOCALAPPDATA%\hermes\patches\anthropic_billing_bypass.py) - Multiple profiles: Hermes supports
profiles/<name>/under the data root. The patch is installed once at the data root and shared by every profile;HERMES_HOMEmay point at a profile dir and the loader still resolves the patch correctly. - Depends on
build_anthropic_kwargs(is_oauth=...)inagent.anthropic_adapter, so it may need updating if hermes-agent changes that interface
After install, confirm Hermes is using the subscription path and not silently billing pay-per-token:
- Startup log — look for these lines in the Hermes gateway log
(
%LOCALAPPDATA%\hermes\logs\on Windows):[anthropic_billing_bypass] Bypass installed [anthropic_billing_bypass] Transport unwrap hook installed [anthropic_billing_bypass] Rate-limit auto-wait installed - No
extra usageerrors — if you seeHTTP 400 You're out of extra usageorHTTP 429 ... extra usage required, the fingerprint drifted and the request was routed to the pay-per-token bucket. Re-runinstall.sh. - Token flow — calls should succeed with
provider=anthropicinagent.logand normal in/out token counts (not 429s).
Dynamic version detection: This build detects the installed Claude Code version automatically (via
claude --version) and uses it for both the user-agent and the signed billing header. No manual pinning — when you update Claude Code, the bypass follows on the next Hermes start. Falls back to_PINNED_CC_VERSION(2.1.112) only if Claude Code isn't on PATH. This replaces the static pin from upstream PR #21 and removes the drift that triggered issue #6.
This build merges the open community PRs into one coherent tree. Where two PRs solved the same problem differently, the newer/better-reasoned design won and the other was folded in or dropped:
- PR #16 — loader moved from
sitecustomize.pyto a.pthshim + bootstrap module, fixing installs that were silently dead on Debian/Ubuntu (the system apportsitecustomize.pywins import priority). - PR #28 — patch resolution across Hermes profiles,
%LOCALAPPDATA%, and$HERMES_HOME, plus dynamic Claude Code version detection and the two-layer auto-recovery (core.hooksPathhooks +restore_loader.shcron). - PR #26 —
$HERMES_HOMEhonoured throughout the shell installers. - PR #24 — Windows PowerShell installers + Credential Manager mirroring,
retargeted onto the
.pthmechanism. Supersedes PR #17. - PR #21 — fingerprint parity (forced
user-agent,x-app). Its multi-interpreter install was ported onto the.pthmechanism so the hook also lands in an editable install used by the CLI. - PR #23 — thinking-block replay integrity, tool-pair repair hardening, and
the
agent.error_classifierhook. Its installer rewrite (--check,.git/hooks/post-merge) was dropped in favour of PR #28's recovery model, which deliberately avoids.git/hooksbecausehermes updatestashes it. - PR #20 — per-pool-entry
account_uuidbilling routing for multi-account credential pools. - PR #1 — clone URL fix.
Not merged:
- PR #17 — strict subset of PR #24.
- PR #7 — MD5 tool-name obfuscation. Upstream never adopted it and still
ships PascalCase
mcp_Bash; this tree keeps the namespaced PascalCase scheme. - PR #10 — stale fingerprint base (branched before v1.5.0) and conflicting.
-
"hermes-agent not found": Make sure Hermes is installed at
$HERMES_HOME/hermes-agent/(defaults to~/.hermes/hermes-agent/) or%LOCALAPPDATA%\hermes\hermes-agent\(Windows) -
"No virtualenv found": Set
HERMES_VENVto point to your venv -
"ModuleNotFoundError: No module named 'anthropic_billing_bypass'" on every startup: the hook couldn't find the patch. This happens when
HERMES_HOMEpoints at a profile directory (.../hermes/profiles/<name>) but the patch lives at the data root. The bootstrap resolves that case automatically — re-run./install.shto pick it up. -
Patch not loading: Check
journalctl --user -u hermes-gateway -n 50(Linux) or the Hermes log under%LOCALAPPDATA%\hermes\logs\(Windows) for[anthropic_billing_bypass]or[hermes-claude-auth]messages -
Bypass silently inactive on Debian/Ubuntu (legacy
sitecustomize.pyinstalls only): If you installed before the.pthmigration, the bypass may never have actually run on your host. Debian/Ubuntu ship/usr/lib/pythonX.Y/sitecustomize.pyfor apport, and that one wins import priority over the venv-localsitecustomize.py, so the hook never installs. Quick diagnostic:cd ~/.hermes/hermes-agent && ./venv/bin/python -c "import anthropic_billing_bypass"
ModuleNotFoundErrormeans the hook isn't running. Fix is to re-run./install.sh— the current installer uses a.pthshim that sidesteps the apport collision and auto-migrates legacy installs. -
PowerShell execution policy: The remote installer (
irm | iex) handles this automatically. For manual runs, usepowershell -ExecutionPolicy Bypass -File .\install.ps1
-
Anthropic 401 authentication failedorNo Anthropic credentials found: Hermes reads Claude subscription credentials from~/.claude/.credentials.json. If Claude Code is authenticated (e.g. in macOS Keychain) but that file is missing or stale, Hermes fails even when Claude Code itself works.On macOS,
install.shv1.1.1+ auto-mirrors theClaude Code-credentialsKeychain entry into~/.claude/.credentials.jsonon every run, so re-running the installer is usually enough. Full fix:- Refresh Claude subscription login:
claude auth login --claudeai
- Re-run the installer to re-mirror credentials (macOS) and reload the patch:
./install.sh
- Remove stale
ANTHROPIC_TOKEN/ANTHROPIC_API_KEYvalues from$HERMES_HOME/.env— they can override subscription auth. - Reset cached credentials:
hermes auth reset anthropic
- Retry with a smoke test:
hermes chat -q 'Reply with exactly: AUTH TEST OK' --provider anthropic -m claude-sonnet-4-6 -Q
If the auto-mirror doesn't work (e.g. your Keychain entry is under a different service name), mirror it manually:
python3 - <<'PY' import subprocess from pathlib import Path secret = subprocess.check_output( ['security', 'find-generic-password', '-s', 'Claude Code-credentials', '-w'], text=True, ).strip() cred_path = Path.home() / '.claude' / '.credentials.json' cred_path.parent.mkdir(parents=True, exist_ok=True) cred_path.write_text(secret) cred_path.chmod(0o600) print(f'wrote {cred_path}') PY
Credit: the macOS Keychain mirror approach was written up by @DrQbz in issue #5 and is now automated in
install.sh.On Windows,
install.ps1auto-mirrors theClaude Code-credentialsentry from Windows Credential Manager into~/.claude/.credentials.jsonusing the Win32 Credential API (analogous to the macOS Keychain mirror). Re-running the installer is usually sufficient:.\install.ps1
If auto-mirroring fails (e.g. PowerShell constrained language mode), re-authenticate and retry:
claude auth login --claudeai .\install.ps1If credentials still aren't found, check Windows Credential Manager manually:
# List all Claude-related credentials cmdkey /list:*Claude* # If no entry exists, re-authenticate: claude auth login --claudeai
- Refresh Claude subscription login:
- HTTP 400: "Third-party apps now draw from your extra usage, not your plan limits": Anthropic's server-side validation has classified your requests as third-party and routed them to pay-per-token credits instead of your Max/Pro plan. Make sure you're on the latest version of this patch (it tracks the upstream opencode-claude-auth fingerprint changes). Reinstall with
./install.shand restarthermes-gateway. If the error persists after update, the bypass is currently broken upstream too — track issue #6 for status. - HTTP 400 persists after update: The billing salt or signature format may have been rotated by Anthropic again. Check for newer commits to this repo.
- Auto-wait won't kick in / 429 reaches the user immediately: the patch
is disabled. Re-enable with
HERMES_RL_AUTOWAIT=1(the default). It requires thesitecustomize.pyhook to be installed (run./install.shand check with./install.sh --check). - Auto-wait bails with "this is bigger than safety-cap" on a weekly limit:
you're on an older build (< v1.5.9). v1.5.9 added window-aware
detection — it now reads
anthropic-ratelimit-unified-7d-status/-resetand waits up toHERMES_RL_AUTOWAIT_MAX_7D_S(default 7.5d). Upgrade:cd ~/hermes-claude-auth && git pull && ./install.sh. - Want a shorter / longer wait for a specific window: tune the
per-window cap via env vars (values in seconds, units in
~/.hermes/.envor your systemd unit):HERMES_RL_AUTOWAIT_MAX_5H_S(default21600= 6h)HERMES_RL_AUTOWAIT_MAX_1D_S(default93600= 26h)HERMES_RL_AUTOWAIT_MAX_7D_S(default648000= 7.5d)HERMES_RL_AUTOWAIT_MAX_S— fallback when the 429 has no window-specific status header (default21600= 6h). Setting any of theMAX_*_Svars to a value<= 0falls back to the default; to disable auto-wait entirely, useHERMES_RL_AUTOWAIT=0.
- griffinmartin/opencode-claude-auth, the original TypeScript implementation for opencode (MIT)
- NousResearch/hermes-agent, the AI agent this patches (MIT)
This uses Claude Code subscription credentials outside the official Claude Code CLI. It works with Anthropic's current OAuth implementation but may break if Anthropic changes their validation. Use at your own risk.
MIT, see LICENSE.