fix: prefer ~/.hermes/.env over stale os.environ when seeding credential pool (salvage #18256)#18755
Merged
Conversation
When _seed_from_env() reads API keys to populate the credential pool, it should treat ~/.hermes/.env as the authoritative source — not os.environ. Stale env vars inherited from parent shell processes (Codex CLI, test scripts, etc.) can shadow deliberate changes to the .env file, causing auth.json to cache an outdated key that leads to silent 401 errors. This is especially visible with OpenRouter: if a parent process exported OPENROUTER_API_KEY=test-key-fresh and the user later updates .env with a valid key, restarting Hermes still picks up the stale os.environ value, writes it back to auth.json, and all API calls fail with 401. Fixes #18254
Follow-up for PR #18256 salvage.
…cedence Covers PR #18256 fix for issue #18254 — when OPENROUTER_API_KEY is set in BOTH os.environ (stale from parent shell) and ~/.hermes/.env (fresh), _seed_from_env must prefer the .env value. Also guards the fallback case where .env omits the key entirely (Docker/K8s/systemd deployments that only inject via runtime env).
This was referenced May 2, 2026
Collaborator
1 similar comment
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When
OPENROUTER_API_KEYis set in both the parent shell (stale) and~/.hermes/.env(fresh),_seed_from_envnow prefers the.envvalue. Before this fix, any stale export from a parent process (Codex CLI, test scripts, previous install) silently overrode the key the user just wrote to.env, wrote the stale value toauth.json, and caused persistent 401 errors with no obvious cause.Cherry-picked from @franksong2702's #18256 onto current main, plus 2 regression tests (precedence + fallback).
What changed
agent/credential_pool.py:_seed_from_envuses a local_get_env_prefer_dotenv()helper that reads~/.hermes/.envfirst, then falls back toos.environ.tests/agent/test_credential_pool.py: 2 regression tests added —test_load_pool_prefers_dotenv_over_stale_os_environcovers the bug.test_load_pool_falls_back_to_os_environ_when_dotenv_emptyguards the Docker/K8s/systemd path where.envis absent and runtime env vars are the only source.Why only in credential-pool seeding (not globally)
The
.envfile is the user's managed Hermes config; inside Hermes' own credential-pool cache seeding, it's authoritative. General env resolution (hermes_cli/env_loader.py) still honors 12-factor precedence (os.environ wins) for runtime-injected secrets — changing that would break Docker/K8s deployments that only set env vars. The narrow scope here only affects which value ends up written toauth.json, not general env resolution.Validation
scripts/run_tests.sh tests/agent/test_credential_pool.py→ 41 passed (39 existing + 2 new).HERMES_HOME, real.envfile, stale shell export ofOPENROUTER_API_KEY, seededauth.jsonwith the stale key. Afterload_pool("openrouter"): selected entry's access_token is the fresh.envvalue, andauth.jsonon disk is rewritten with the fresh key.Closes #18254.