Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(deploy): use Claude Max OAuth (CLAUDE_CODE_OAUTH_TOKEN) on the eligia VPS + fix stale IP in README#11

Open
Wizarck wants to merge 2 commits into
mainfrom
feat/deploy-anthropic-oauth-max
Open

feat(deploy): use Claude Max OAuth (CLAUDE_CODE_OAUTH_TOKEN) on the eligia VPS + fix stale IP in README#11
Wizarck wants to merge 2 commits into
mainfrom
feat/deploy-anthropic-oauth-max

Conversation

@Wizarck

@Wizarck Wizarck commented May 21, 2026

Copy link
Copy Markdown
Owner

Summary

Two related deploy-only changes — no source/library code touched.

  1. docker-compose.yml: swap ANTHROPIC_API_KEY for CLAUDE_CODE_OAUTH_TOKEN so the native anthropic provider uses the Bearer OAuth path against Claude Max instead of x-api-key against the API-credits lane.
  2. README.md: correct the eligia VPS IP from 178.104.140.21 (retired May 2026) to 62.238.3.180 — the Hetzner CX43 (eligia-stockgrab-cx43-20260516-2116) where /opt/hermes/ actually lives today.

Why the env-var swap is non-trivial (one-line caveat)

The anthropic plugin profile (plugins/model-providers/anthropic/__init__.py:47) declares env_vars=(\"ANTHROPIC_API_KEY\", \"ANTHROPIC_TOKEN\", \"CLAUDE_CODE_OAUTH_TOKEN\") and resolves left-to-right — ANTHROPIC_API_KEY always wins over CLAUDE_CODE_OAUTH_TOKEN when both are set. Removing the ANTHROPIC_API_KEY env line is required, not optional.

Evidence the OAuth path actually works for Max + tools

Tested 2026-05-21 from local against https://api.anthropic.com/v1/messages with a fresh Claude Max 20x OAuth token from ~/.claude/.credentials.json:

Model tools Mimicry headers Result Headers
claude-haiku-4-5-20251001 no yes 200 OK unified-5h: 0.04, unified-7d: 0.99, unified-status: allowed_warning
claude-haiku-4-5-20251001 yes yes 200 OK same → routed to Max lane
claude-haiku-4-5-20251001 yes no (just oauth-2025-04-20 beta) 200 OK same — mimicry is not load-bearing for Haiku
claude-opus-4-7 / claude-opus-4-6 / claude-sonnet-4-6 / claude-sonnet-4-5 yes / no yes 429 account at 99% weekly Max cap; inconclusive vs upstream issue NousResearch#15080

Since deploy/eligia-vps/config.yaml defaults to claude-haiku-4-5-20251001 for this deployment, the model that's actually in use is the one the testing confirms works. Opus/Sonnet behaviour is unknown from this run; if anything escalates to those tiers via fallback, the existing OpenRouter fallback chain in config.yaml will catch it.

Companion VPS work (NOT in this PR — runbook)

After merge, on [email protected]:

  1. Generate a long-lived OAuth token locally with `claude setup-token` (interactive, browser flow).
  2. Edit the SOPS-encrypted secrets file:
    SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt \
      sops /opt/eligia/eligia-core/secrets/secrets.env
    Add: CLAUDE_CODE_OAUTH_TOKEN_HERMES=<token from step 1>. Optionally keep ANTHROPIC_API_KEY_HERMES= as an emergency fallback (this compose file no longer reads it).
  3. Sync the new compose file: scp deploy/eligia-vps/docker-compose.yml [email protected]:/opt/hermes/docker-compose.yml
  4. Force recreate (matches hermes.service's ExecStart):
    cd /opt/hermes && sops exec-env /opt/eligia/eligia-core/secrets/secrets.env 'docker compose up -d --force-recreate'
  5. Verify: docker logs hermes --tail 50 should show auth as OAuth/Bearer, not x-api-key. Send a Telegram test message to confirm end-to-end.
  6. Once stable, revoke the old ANTHROPIC_API_KEY on the Anthropic console — it remains valid until then.

Follow-ups outside this PR

Test plan

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.7 (1M context) [email protected]

Summary by CodeRabbit

  • Documentation

    • Updated production deployment documentation for the Hermes service with current host information and revised disaster recovery guidance.
  • Chores

    • Migrated the Hermes service authentication to use OAuth bearer token authentication with updated environment configuration.

Review Change Stack

Wizarck and others added 2 commits May 21, 2026 02:09
…ermes uses Claude Max via OAuth on the eligia VPS

Plugin resolves env_vars left-to-right (plugins/model-providers/anthropic/__init__.py:47) and ANTHROPIC_API_KEY wins over CLAUDE_CODE_OAUTH_TOKEN if both are set, so the API key has to be removed (not just shadowed) for the Bearer OAuth path to fire.

Verified 2026-05-21 via direct calls to api.anthropic.com:
- Haiku 4.5 + tools + OAuth Max -> HTTP 200, response carries anthropic-ratelimit-unified-{5h,7d}-utilization headers and unified-status: allowed_warning (= Max-lane traffic, not overage).
- Mimicry headers (x-app: cli, user-agent: claude-cli/..., extra betas) make no difference for Haiku, both paths route to Max.
- Opus 4.7 and Sonnet 4.6 returned HTTP 429 (account already at 99% weekly Max), so issue NousResearch#15080 status for those tiers is inconclusive from this run. This deploy stays on Haiku 4.5 default (config.yaml unchanged) where the routing is known good.

Companion VPS work (separate from this PR): SOPS-encrypted /opt/eligia/eligia-core/secrets/secrets.env must add CLAUDE_CODE_OAUTH_TOKEN_HERMES=<long-lived token from `claude setup-token`>; the ANTHROPIC_API_KEY_HERMES variable can stay defined as an emergency fallback (this compose file no longer reads it).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…78.104.140.21 retirement

The old 178.104.140.21 box (now hostname geeplo-prod) was rebuilt 2026-05-17 without hermes. The actual hermes prod runs on the Hetzner CX43 eligia-stockgrab-cx43-20260516-2116 (62.238.3.180) under the same /opt/hermes/ layout the rest of this README still describes. Companion runbook update in eligia-core/runbooks/hermes-hitl-deploy.md is required separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request updates the Eligia VPS production deployment configuration. It replaces the legacy host IP address with a new Hetzner VPS address in the README documentation and updates the Hermes service container to use Claude Max OAuth token authentication instead of an Anthropic API key in the docker-compose configuration.

Changes

Eligia VPS Production Deployment Update

Layer / File(s) Summary
VPS host documentation and deployment context
deploy/eligia-vps/README.md
Updated the README's production Hermes deployment section with the new Hetzner VPS host IP (62.238.3.180), hostname (eligia-stockgrab-cx43-20260516-2116), and revised the disaster-recovery/versioning narrative to reflect the current deployment timeline.
Hermes service authentication configuration
deploy/eligia-vps/docker-compose.yml
Replaced ANTHROPIC_API_KEY with CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN_HERMES} in the hermes service environment, added inline comments documenting the OAuth routing precedence for Max token selection.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • Wizarck/hermes-agent#3: This PR builds on the initial introduction of the deploy/eligia-vps/ files, updating the host IP, hostname, and authentication mechanism for the production deployment.

Poem

🐰 A hop and a skip to Hetzner's new nest,
OAuth tokens where API keys once rested,
The VPS address updated with care,
Production Hermes now deployed from there! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes both main changes: switching to Claude Max OAuth in docker-compose.yml and updating the stale IP address in README.md.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deploy-anthropic-oauth-max

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

🔎 Lint report: feat/deploy-anthropic-oauth-max vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8279 on HEAD, 8279 on base (➖ 0)

🆕 New issues (14):

Rule Count
invalid-argument-type 8
unresolved-attribute 3
unsupported-operator 3
First entries
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `startswith` is not defined on `dict[str, str]` in union `Unknown | str | dict[str, str]`
tests/run_agent/test_provider_attribution_headers.py:155: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["X-OpenRouter-Cache"]` and `Unknown | str | dict[str, str] | ... omitted 3 union elements`
run_agent.py:7482: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
run_agent.py:2433: [invalid-argument-type] invalid-argument-type: Argument to function `query_ollama_num_ctx` is incorrect: Expected `str`, found `(str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 4 union elements`
run_agent.py:13764: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str & ~AlwaysFalsy`, `int & ~AlwaysFalsy` in union `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 3 union elements`
tests/run_agent/test_provider_attribution_headers.py:156: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["X-OpenRouter-Cache-TTL"]` and `Unknown | str | dict[str, str] | ... omitted 3 union elements`
run_agent.py:2740: [invalid-argument-type] invalid-argument-type: Argument to function `get_model_context_length` is incorrect: Expected `str`, found `str | dict[str, str] | Any | ... omitted 3 union elements`
tests/run_agent/test_provider_attribution_headers.py:90: [unresolved-attribute] unresolved-attribute: Attribute `startswith` is not defined on `dict[str, str]` in union `Unknown | str | dict[str, str]`
run_agent.py:7311: [invalid-argument-type] invalid-argument-type: Argument to function `_codex_cloudflare_headers` is incorrect: Expected `str`, found `Unknown | str | dict[str, str] | ... omitted 3 union elements`
run_agent.py:2689: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 4 union elements`
run_agent.py:13767: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown, Unknown] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`
tests/agent/test_codex_cloudflare_headers.py:181: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["originator"]` and `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:2692: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 4 union elements`

✅ Fixed issues (14):

Rule Count
invalid-argument-type 8
unsupported-operator 3
unresolved-attribute 3
First entries
run_agent.py:2740: [invalid-argument-type] invalid-argument-type: Argument to function `get_model_context_length` is incorrect: Expected `str`, found `str | dict[str, str] | Any | ... omitted 4 union elements`
run_agent.py:2433: [invalid-argument-type] invalid-argument-type: Argument to function `query_ollama_num_ctx` is incorrect: Expected `str`, found `(str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 5 union elements`
run_agent.py:7311: [invalid-argument-type] invalid-argument-type: Argument to function `_codex_cloudflare_headers` is incorrect: Expected `str`, found `Unknown | str | dict[str, str] | ... omitted 4 union elements`
run_agent.py:2689: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 5 union elements`
tests/agent/test_codex_cloudflare_headers.py:181: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["originator"]` and `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 4 union elements`
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `startswith` is not defined on `dict[str, str]` in union `Unknown | str | Divergent | dict[str, str]`
run_agent.py:13767: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown, Unknown] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 4 union elements`
tests/agent/test_codex_cloudflare_headers.py:163: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str & ~AlwaysFalsy`, `int & ~AlwaysFalsy` in union `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 4 union elements`
tests/run_agent/test_provider_attribution_headers.py:156: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["X-OpenRouter-Cache-TTL"]` and `Unknown | str | dict[str, str] | ... omitted 4 union elements`
tests/run_agent/test_provider_attribution_headers.py:90: [unresolved-attribute] unresolved-attribute: Attribute `startswith` is not defined on `dict[str, str]` in union `Unknown | str | Divergent | dict[str, str]`
tests/run_agent/test_provider_attribution_headers.py:155: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["X-OpenRouter-Cache"]` and `Unknown | str | dict[str, str] | ... omitted 4 union elements`
run_agent.py:2692: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | ... omitted 5 union elements`
run_agent.py:7482: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 4 union elements`
run_agent.py:13764: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 4 union elements`

Unchanged: 4312 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
deploy/eligia-vps/README.md (1)

89-139: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Stale IP address in all operational commands.

The introduction was updated to document the new VPS IP (62.238.3.180), but all SSH commands throughout the file still reference the retired host (178.104.140.21). Every documented operation will fail.

Lines affected:

  • Line 89 (upstream sync routine)
  • Lines 106, 110, 116, 119 (build + deploy)
  • Line 131 (migration script)
  • Line 139 (verify Langfuse tracing)
🔧 Proposed fix: update all SSH commands to the new IP

Apply this replacement to lines 89, 106, 110, 116, 119, 131, and 139.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/eligia-vps/README.md` around lines 89 - 139, The README contains
multiple ssh command invocations still pointing at the old VPS IP
178.104.140.21; update every SSH invocation in the documented blocks (the git
pull/upstream-sync command, the three-step Build + deploy ssh lines that run git
pull, docker build, systemctl restart hermes and the docker inspect health
check, and the migration script curl command referencing
migrate-from-wamba-build.sh) to use the new IP 62.238.3.180 so all examples run
against the current host.
🧹 Nitpick comments (1)
deploy/eligia-vps/docker-compose.yml (1)

24-24: ⚡ Quick win

Consider documenting Issue #15080 inline or linking to tracking.

The comment references "Issue #15080 may still bite Opus/Sonnet" but doesn't explain what this issue is. Future operators may not know whether this is an Anthropic API issue, a Hermes plugin issue, or something else.

💡 Suggested clarification
-    # Issue `#15080` may still bite Opus/Sonnet; this deploy uses Haiku.
+    # Anthropic API Issue `#15080` (OAuth routing for non-Haiku models) may
+    # still bite Opus/Sonnet; this deploy uses Haiku to avoid it.

Alternatively, if this is a GitHub issue in this repo or upstream, add the full reference (e.g., nousresearch/hermes-agent#15080 or URL).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/eligia-vps/docker-compose.yml` at line 24, Update the inline comment
in docker-compose.yml that reads "Issue `#15080` may still bite Opus/Sonnet; this
deploy uses Haiku." to either include a brief one-sentence explanation of the
problem (e.g., what breaks and under what conditions) and/or a full issue
reference or URL (for example "nousresearch/hermes-agent#15080" or the full
GitHub URL) so future operators know the source and can follow the tracker;
modify the exact comment string in the file to add the explanation or link.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@deploy/eligia-vps/README.md`:
- Around line 89-139: The README contains multiple ssh command invocations still
pointing at the old VPS IP 178.104.140.21; update every SSH invocation in the
documented blocks (the git pull/upstream-sync command, the three-step Build +
deploy ssh lines that run git pull, docker build, systemctl restart hermes and
the docker inspect health check, and the migration script curl command
referencing migrate-from-wamba-build.sh) to use the new IP 62.238.3.180 so all
examples run against the current host.

---

Nitpick comments:
In `@deploy/eligia-vps/docker-compose.yml`:
- Line 24: Update the inline comment in docker-compose.yml that reads "Issue
`#15080` may still bite Opus/Sonnet; this deploy uses Haiku." to either include a
brief one-sentence explanation of the problem (e.g., what breaks and under what
conditions) and/or a full issue reference or URL (for example
"nousresearch/hermes-agent#15080" or the full GitHub URL) so future operators
know the source and can follow the tracker; modify the exact comment string in
the file to add the explanation or link.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e5517e8-b084-454a-b377-26a3f7e5ecb0

📥 Commits

Reviewing files that changed from the base of the PR and between 8a52206 and af91a39.

📒 Files selected for processing (2)
  • deploy/eligia-vps/README.md
  • deploy/eligia-vps/docker-compose.yml

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant