Skip to content

fix(credentials): prefer ~/.hermes/.env over stale os.environ on key rotation (#20591) - #55528

Merged
teknium1 merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/20591-prefer-dotenv-on-resolve
Jun 30, 2026
Merged

teknium1 merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/20591-prefer-dotenv-on-resolve

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Contributor

Summary

A rotated API key in ~/.hermes/.env now wins over a stale value still exported in the parent shell, closing the remaining path that produced persistent 401s after key rotation.

Background

#20591 was closed as fixed, but only the credential-pool seeding path was corrected (#18254/#18755). The live request-time resolution path was still broken: _resolve_api_key_provider_secret (hermes_cli/auth.py) resolved keys via get_env_value(), which returns the os.environ value first. So after a .env rotation, the pool re-seeded with the fresh key while the resolution path kept returning the stale shell export → 401s on every request.

Verified live on current main before this fix:

_resolve_api_key_provider_secret("deepseek") -> sk-STALE-from-shell   (stale wins — bug)

Changes

  • hermes_cli/config.py: add get_env_value_prefer_dotenv() — checks ~/.hermes/.env first, then os.environ. Distinct from get_env_value() (unchanged, os.environ-first) so only Hermes-managed credential resolution flips precedence; the generic helper's many other callers are unaffected.
  • hermes_cli/auth.py: _resolve_api_key_provider_secret resolves through the new helper.
  • tests/: regression coverage for both the pool-seeding path and the auth-resolution path (a rotated .env key must beat a stale shell export).

Validation

Before After
_resolve_api_key_provider_secret (rotated .env, stale shell) stale shell key rotated .env key
get_env_value() (generic helper) os.environ-first os.environ-first (unchanged)
  • 91 tests pass across tests/tools/test_credential_pool_env_fallback.py + tests/agent/test_credential_pool.py (89 prior + 2 new regressions); ruff clean.
  • E2E against a real resolution path (isolated HERMES_HOME, .env vs os.environ): the rotated key now wins, with a negative control confirming get_env_value() is unchanged (no blast-radius regression).

Credit

Salvage of #20602 by @0xDevNinja, who located the exact still-broken path (_resolve_api_key_provider_secretget_env_value) that the earlier pool-only fix didn't cover. Cherry-picked to preserve authorship; rebased onto current main (the original was ~5.5k commits behind). The PR's credential_pool.py change was dropped — that path already prefers .env on current main (via secret_scope), so the substantive fix is config.py + auth.py only.

Closes #20591.

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data P1 High — major feature broken, no workaround duplicate This issue or pull request already exists labels Jun 30, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Duplicate of #20602 — identical fix for the credential-rotation 401 (#20591): adds get_env_value_prefer_dotenv() to hermes_cli/config.py and routes _resolve_api_key_provider_secret (hermes_cli/auth.py) through it. #20602 is the earlier open PR and additionally covers the agent/credential_pool.py path, so it's the broader canonical version. Also related: #20929 (extends prefer-dotenv to the remaining resolve paths).

…rotation

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.
@kshitijk4poor
kshitijk4poor force-pushed the salvage/20591-prefer-dotenv-on-resolve branch from de37eda to 1c78354 Compare June 30, 2026 09:45
@teknium1
teknium1 enabled auto-merge (squash) June 30, 2026 09:47
@teknium1
teknium1 merged commit 26f39f7 into NousResearch:main Jun 30, 2026
29 checks passed
dtera pushed a commit to dtera/hermes-agent that referenced this pull request Jul 1, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
Jasper6439 pushed a commit to Jasper6439/hermes-agent that referenced this pull request Jul 5, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
@kshitijk4poor
kshitijk4poor deleted the salvage/20591-prefer-dotenv-on-resolve branch August 5, 2026 07:09
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…rotation (NousResearch#55528)

`_resolve_api_key_provider_secret` resolved API keys via `get_env_value`,
which returns the `os.environ` value first and only falls back to
`~/.hermes/.env`. After a user rotates a key in `.env`, a stale value still
exported in the parent shell (Codex CLI, test runner, login profile) shadows
the fresh key on every request, producing persistent 401s.

The credential-pool seeding path was already fixed to prefer `.env`
(NousResearch#18254/NousResearch#18755), but the live request-time resolution path was not — so the
pool re-seeded with the fresh key while `_resolve_api_key_provider_secret`
kept returning the stale shell export. This closes that remaining path.

- config: add `get_env_value_prefer_dotenv()` — checks `~/.hermes/.env`
  first, then `os.environ`. Distinct from `get_env_value()` (unchanged,
  os.environ-first) so only Hermes-managed credential resolution flips
  precedence; the generic helper's many callers are unaffected.
- auth: `_resolve_api_key_provider_secret` resolves through the new helper.
- tests: regression coverage for both the pool-seeding path and the
  auth resolution path (a rotated `.env` key must beat a stale shell export).

Closes NousResearch#20591.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
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 duplicate This issue or pull request already exists P1 High — major feature broken, no workaround sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: credential pool reads stale os.environ instead of fresh .env — test documents correct behavior but code doesn't match

4 participants