Skip to content

fix(secrets): only apply external secrets once per HERMES_HOME per process#32271

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-dff876f1
May 25, 2026
Merged

fix(secrets): only apply external secrets once per HERMES_HOME per process#32271
teknium1 merged 1 commit into
mainfrom
hermes/hermes-dff876f1

Conversation

@teknium1

@teknium1 teknium1 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

load_hermes_dotenv() is called at module-import time from at least seven hot modules (cli.py, hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py, tui_gateway/server.py, acp_adapter/entry.py). Each call re-ran _apply_external_secret_sources(), so users with Bitwarden enabled saw

  Bitwarden Secrets Manager: applied 1 secret (ANTHROPIC_API_KEY)
  Bitwarden Secrets Manager: applied 1 secret (ANTHROPIC_API_KEY)
  Bitwarden Secrets Manager: applied 1 secret (ANTHROPIC_API_KEY)
  ...

3-5x per CLI startup. Bitwarden's 300s in-process cache absorbed the network call, but the config re-parse, ASCII sanitization sweep, and stderr print all still ran every time.

Changes

  • hermes_cli/env_loader.py: track a process-level set of HERMES_HOME paths that have already had external secrets applied; short-circuit subsequent calls.
  • reset_secret_source_cache() public helper for tests and any future long-running consumer that wants to refresh after a config change (e.g. hermes secrets bitwarden sync from a daemon).
  • Test fixture clears the new guard between tests; regression test asserts 5 import-time calls produce exactly 1 backend hit + 1 status line.

Validation

Before After
Bitwarden status prints per startup 3-5 1
Backend calls per startup 3-5 (cache absorbs network) 1
Targeted tests 6 pass 8 pass (1 new regression test)
E2E (isolated HERMES_HOME, 5 simulated import-time calls) 5 prints 1 print

Infographic

bitwarden-spam-silenced

…ocess

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-dff876f1 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9349 on HEAD, 9349 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4948 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/perf Performance improvement or optimization P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 25, 2026
@teknium1 teknium1 merged commit de76f4d into main May 25, 2026
26 checks passed
@teknium1 teknium1 deleted the hermes/hermes-dff876f1 branch May 25, 2026 22:18
bridge25 pushed a commit to bridge25/hermes-agent that referenced this pull request May 27, 2026
…ocess (NousResearch#32271)

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
mathias3 pushed a commit to mathias3/hermes-agent that referenced this pull request May 28, 2026
…ocess (NousResearch#32271)

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
shuv1337 pushed a commit to shuv1337/hermes-agent that referenced this pull request May 28, 2026
…ocess (NousResearch#32271)

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
Bryce-huang pushed a commit to wbkunlun/hermes-agent that referenced this pull request May 29, 2026
…ocess (NousResearch#32271)

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
#AI commit#
mosaiq-systems pushed a commit to mosaiq-systems/hermes-agent that referenced this pull request May 29, 2026
…ocess (NousResearch#32271)

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…ocess (NousResearch#32271)

`load_hermes_dotenv()` is called at module-import time from cli.py,
hermes_cli/main.py, run_agent.py, trajectory_compressor.py, gateway/run.py,
tui_gateway/server.py, acp_adapter/entry.py, and a few others. Each call
triggered `_apply_external_secret_sources()`, which re-parsed config,
re-fetched from Bitwarden Secrets Manager (its own 300s cache mostly absorbed
this), re-ran the ASCII sanitization sweep, and reprinted

  Bitwarden Secrets Manager: applied N secret(s) (...)

to stderr. Users saw the status line 3-5x per CLI startup.

Guard the function with a process-level set of HERMES_HOME paths that have
already had external secrets applied. Subsequent calls for the same home_path
are no-ops. `reset_secret_source_cache()` lets tests (and any future
long-running consumer that wants to refresh after a config change) force a
re-pull.
briandevans added a commit to briandevans/hermes-agent that referenced this pull request Jun 2, 2026
The cron scheduler called bare `dotenv.load_dotenv()` and never invoked
the BSM apply path. Any cron job that needed a BSM-managed credential
(Discord token, provider API key, etc.) saw the .env placeholder string
instead of the real value and failed with HTTP 401, while the gateway
and the CLI happily resolved the same secrets via `load_hermes_dotenv()`.

Add a follow-up call to `_apply_external_secret_sources(_get_hermes_home())`
after the dotenv reload so cron picks up the same secrets the gateway
does. The `_APPLIED_HOMES` process-level dedup added in NousResearch#32271 (de76f4d)
makes calling this on every tick effectively free after the first pull.
A failing BSM backend must never block job execution, so the call is
wrapped in a debug-logged try/except.

This is the minimal additive change rather than the full substitution
of `load_hermes_dotenv()`: existing tests patch `dotenv.load_dotenv`
directly via 19 sites, and replacing the dotenv call would change
path-loading semantics (project_env loading) the scheduler has never
exercised. The bug is specifically missing BSM resolution; this fix
covers exactly that.

Fixes NousResearch#33465
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have type/perf Performance improvement or optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants