feat(deploy): use Claude Max OAuth (CLAUDE_CODE_OAUTH_TOKEN) on the eligia VPS + fix stale IP in README#11
feat(deploy): use Claude Max OAuth (CLAUDE_CODE_OAUTH_TOKEN) on the eligia VPS + fix stale IP in README#11Wizarck wants to merge 2 commits into
Conversation
…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]>
📝 WalkthroughWalkthroughThe 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. ChangesEligia VPS Production Deployment Update
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
🔎 Lint report:
|
| 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.
There was a problem hiding this comment.
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 winCritical: 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
-ssh [email protected] \ +ssh [email protected] \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 winConsider documenting Issue
#15080inline or linking to tracking.The comment references "Issue
#15080may 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#15080or 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
📒 Files selected for processing (2)
deploy/eligia-vps/README.mddeploy/eligia-vps/docker-compose.yml
Summary
Two related deploy-only changes — no source/library code touched.
docker-compose.yml: swapANTHROPIC_API_KEYforCLAUDE_CODE_OAUTH_TOKENso the native anthropic provider uses the Bearer OAuth path against Claude Max instead of x-api-key against the API-credits lane.README.md: correct the eligia VPS IP from178.104.140.21(retired May 2026) to62.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) declaresenv_vars=(\"ANTHROPIC_API_KEY\", \"ANTHROPIC_TOKEN\", \"CLAUDE_CODE_OAUTH_TOKEN\")and resolves left-to-right —ANTHROPIC_API_KEYalways wins overCLAUDE_CODE_OAUTH_TOKENwhen both are set. Removing theANTHROPIC_API_KEYenv 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/messageswith a fresh Claude Max 20x OAuth token from~/.claude/.credentials.json:toolsclaude-haiku-4-5-20251001unified-5h: 0.04,unified-7d: 0.99,unified-status: allowed_warningclaude-haiku-4-5-20251001claude-haiku-4-5-20251001oauth-2025-04-20beta)claude-opus-4-7/claude-opus-4-6/claude-sonnet-4-6/claude-sonnet-4-5Since
deploy/eligia-vps/config.yamldefaults toclaude-haiku-4-5-20251001for 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 inconfig.yamlwill catch it.Companion VPS work (NOT in this PR — runbook)
After merge, on
[email protected]:SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt \ sops /opt/eligia/eligia-core/secrets/secrets.envCLAUDE_CODE_OAUTH_TOKEN_HERMES=<token from step 1>. Optionally keepANTHROPIC_API_KEY_HERMES=as an emergency fallback (this compose file no longer reads it).scp deploy/eligia-vps/docker-compose.yml [email protected]:/opt/hermes/docker-compose.ymlhermes.service'sExecStart):docker logs hermes --tail 50should show auth as OAuth/Bearer, not x-api-key. Send a Telegram test message to confirm end-to-end.ANTHROPIC_API_KEYon the Anthropic console — it remains valid until then.Follow-ups outside this PR
Wizarck/eligia-corerunbooks/hermes-hitl-deploy.md— every[email protected]reference needs the same IP update.400 \"out of extra usage\", surface a clearer error or route those tiers through OpenRouter automatically.Test plan
anthropic-ratelimit-unified-5h-utilizationheaders visible in container logs / Langfuse traces, confirming the Max-lane path is live in prod.🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.7 (1M context) [email protected]
Summary by CodeRabbit
Documentation
Chores