Skip to content

fix: prefer ~/.hermes/.env over os.environ when seeding credential pool#18256

Closed
franksong2702 wants to merge 1 commit into
NousResearch:mainfrom
franksong2702:fix/auth-json-credential-cache-invalidates-env
Closed

fix: prefer ~/.hermes/.env over os.environ when seeding credential pool#18256
franksong2702 wants to merge 1 commit into
NousResearch:mainfrom
franksong2702:fix/auth-json-credential-cache-invalidates-env

Conversation

@franksong2702

@franksong2702 franksong2702 commented May 1, 2026

Copy link
Copy Markdown

Summary

When _seed_from_env() reads API keys to populate the credential pool in auth.json, it was using get_env_value() which prefers os.environ over ~/.hermes/.env. This means stale env vars inherited from parent shell processes (Codex CLI, test scripts, etc.) can shadow deliberate changes to the .env file.

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 OpenRouter API calls silently fail with 401.

Changes

  1. Added a local helper _get_env_prefer_dotenv() in _seed_from_env() that reads from ~/.hermes/.env (via load_env()) first, then falls back to os.environ
  2. Replaced all 3 get_env_value() call sites in _seed_from_env() with the new helper
  3. Added import os and load_env imports

Root Cause

get_env_value() in hermes_cli/config.py checks os.environ FIRST, then falls back to the .env file:

def get_env_value(key: str) -> Optional[str]:
    if key in os.environ:
        return os.environ[key]  # <-- takes priority over .env!
    env_vars = load_env()
    return env_vars.get(key)

This is a reasonable default for most call sites (runtime config), but for _seed_from_env() — which populates the persistent credential cache — the .env file should be authoritative. The credential cache already persists the snapshot, so the env var is only relevant during seeding; using a potentially stale os.environ value here defeats the purpose of the persistent cache.

Additional Discovery

During testing, another source of stale keys was identified: Hermes profile .env files at ~/.hermes/profiles/<name>/.env. These are created by hermes profile create --clone which copies the main .env at creation time. If the main .env had a test/stale key at that point, the profile retains it even after the main .env is updated. This is a separate concern from the os.environ priority issue, but users should be aware that profile .env files may also need manual sync when rotating keys.

Test Plan

  • Syntax verification: python3 -c "import py_compile; py_compile.compile('agent/credential_pool.py', doraise=True)"
  • Manual: set a key in .env, export a different value in shell, restart Hermes → should use .env value
  • Existing credential pool tests should pass

Related Issues

Closes #18254

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 NousResearch#18254
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround area/auth Authentication, OAuth, credential pools comp/agent Core agent loop, run_agent.py, prompt builder labels May 1, 2026
teknium1 added a commit that referenced this pull request May 2, 2026
teknium1 added a commit that referenced this pull request May 2, 2026
…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).
@teknium1

teknium1 commented May 2, 2026

Copy link
Copy Markdown
Contributor

Merged via #18755 — your commit cherry-picked onto current main with authorship preserved via rebase-merge. Added regression tests for both the bug case (stale shell export shadowing fresh .env) and the fallback case (runtime-injected env in Docker/K8s deployments without a .env file). Thanks for the clean narrow fix!

#18755

@teknium1 teknium1 closed this May 2, 2026
nickdlkk pushed a commit to nickdlkk/hermes-agent that referenced this pull request May 11, 2026
nickdlkk pushed a commit to nickdlkk/hermes-agent that referenced this pull request May 11, 2026
…cedence

Covers PR NousResearch#18256 fix for issue NousResearch#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).
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…cedence

Covers PR NousResearch#18256 fix for issue NousResearch#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).
dannyJ848 pushed a commit to dannyJ848/hermes-agent that referenced this pull request May 17, 2026
dannyJ848 pushed a commit to dannyJ848/hermes-agent that referenced this pull request May 17, 2026
…cedence

Covers PR NousResearch#18256 fix for issue NousResearch#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).
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…cedence

Covers PR NousResearch#18256 fix for issue NousResearch#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).
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
…cedence

Covers PR NousResearch#18256 fix for issue NousResearch#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).
Seven74AI pushed a commit to Seven74AI/hermes-agent that referenced this pull request Jun 13, 2026
Seven74AI pushed a commit to Seven74AI/hermes-agent that referenced this pull request Jun 13, 2026
…cedence

Covers PR NousResearch#18256 fix for issue NousResearch#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).
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/agent Core agent loop, run_agent.py, prompt builder P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: auth.json credential cache ignores .env changes — stale key persists

3 participants