Skip to content

feat(auth): HERMES_AUTH_FILE env var to share auth.json across profiles - #20405

Closed
AShahver wants to merge 1 commit into
NousResearch:mainfrom
AShahver:feat/hermes-auth-file-env-var
Closed

feat(auth): HERMES_AUTH_FILE env var to share auth.json across profiles#20405
AShahver wants to merge 1 commit into
NousResearch:mainfrom
AShahver:feat/hermes-auth-file-env-var

Conversation

@AShahver

@AShahver AShahver commented May 5, 2026

Copy link
Copy Markdown

Problem

When several Hermes profiles share a single OAuth account (very common with ChatGPT-account-backed Codex auth), the per-profile ~/.hermes/profiles/<name>/auth.json files all start out with the same refresh token. Refresh tokens are single-use and rotated on every successful refresh, so the first profile to refresh invalidates the others' tokens. The other 7-of-8 profiles then silently keep the dead token and start failing with:

Codex refresh token was already consumed by another client (e.g. Codex CLI or VS Code extension).

…once their access tokens expire ~10 days later. There's no recovery short of running the interactive hermes auth device-code flow on each broken profile — which then immediately re-races on the next refresh.

Fix

Add a HERMES_AUTH_FILE env var that overrides _auth_file_path(). Set it on every profile's gateway plist + login shell, point all profiles at one shared path (e.g. ~/.hermes/shared/auth.json), and the existing _auth_store_lock — which is keyed off <auth_file>.lock — automatically serializes refreshes across all consumers because they all now compute the same lock path.

No new locking primitive, no fan-out daemon, no token-copy cron. The flock that's already there does the right thing once the paths align.

Diff shape

  • hermes_cli/auth.py — 9-line addition at the top of _auth_file_path(). Falls through to existing per-HERMES_HOME behavior if the env var is unset/blank.
  • tests/conftest.py — adds HERMES_AUTH_FILE to _HERMES_BEHAVIORAL_VARS so production env doesn't leak into the test suite.
  • tests/hermes_cli/test_auth_file_path.py — 7 new unit tests covering: default fall-through, env override, ~ expansion, blank override, lock-path follows override, cross-profile state sharing, and identical lock paths from two profiles.

Verification

$ pytest tests/hermes_cli/test_auth_file_path.py tests/hermes_cli/test_auth_codex_provider.py tests/hermes_cli/test_auth_commands.py tests/hermes_cli/test_auth_provider_gate.py -q
73 passed in 3.06s

Verified end-to-end on an 8-profile production fleet: applied locally, all profiles now share ~/.hermes/shared/auth.json, force-refresh from one profile rotated the token, all other profiles immediately observed the rotation, and a Codex cron that had been failing with the consumed-token error for 5 days completed successfully on the next tick.

Compatibility

Pure addition. Default behavior (env var unset) is unchanged — get_hermes_home() / "auth.json" still resolves exactly the same way. No migration needed.

Allows multiple Hermes profiles (or processes on different hosts sharing a
network mount) to point at one auth.json by setting HERMES_AUTH_FILE. This
is required when several profiles share a single OAuth account: refresh
tokens are single-use and rotated on every successful refresh, so without
a shared file the first profile to refresh invalidates the others' tokens.

With a shared path the existing fcntl flock in `_auth_store_lock` (keyed
off `<auth_file>.lock`) automatically serializes refreshes across all
consumers — no new locking primitive needed.

Use case: an 8-profile Hermes deployment on one host, all using the same
ChatGPT account. Pre-fix, 7 of 8 profiles would silently get stuck on the
dead refresh token and start failing with "Codex refresh token was already
consumed by another client" once their access tokens expired.

`tests/conftest.py` adds HERMES_AUTH_FILE to the behavioral-vars blanking
list so production env doesn't leak into tests.

7 new unit tests in `tests/hermes_cli/test_auth_file_path.py` cover:
- default fall-through to HERMES_HOME/auth.json
- override honors path verbatim
- ~ expansion in the override
- whitespace-only override falls through (treats blank as unset)
- _auth_lock_path follows the override (the property that makes flock work)
- two distinct HERMES_HOME values + one HERMES_AUTH_FILE share state
- two profiles compute the same lock path
@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard labels May 5, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused reproduction and for tracing the existing lock behavior. This is an automated hermes-sweeper review.

  • AGENTS.md:102-106 has a standing policy that new non-secret HERMES_* behavioral configuration must use config.yaml, not a user-facing environment variable.
  • This PR's mechanism is specifically a new HERMES_AUTH_FILE override in hermes_cli/auth.py; the PR description asks operators to set it in gateway plists and login shells.
  • The underlying shared-auth concern is separately tracked in Profiled workers need a shared auth home separate from isolated HERMES_HOME #29530. A focused, configuration-backed design can be considered there without adding a new user-facing environment variable.

Closing this PR under that standing configuration policy.


Closed as not-planned per standing maintainer policy (env-var-for-config). This is a design-direction decision, not a code-quality judgment — see the Contribution Rubric in AGENTS.md for what the project is looking for. If you believe this policy was misapplied to your change, comment here and a maintainer will take a look.

@teknium1 teknium1 closed this Jul 12, 2026
@teknium1 teknium1 added sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants