Skip to content

fix(cron): resolve BSM secrets in scheduler run_job (#33465)#33667

Closed
trac3r00 wants to merge 1 commit into
NousResearch:mainfrom
trac3r00:fix/cron-bsm-resolution-33465
Closed

fix(cron): resolve BSM secrets in scheduler run_job (#33465)#33667
trac3r00 wants to merge 1 commit into
NousResearch:mainfrom
trac3r00:fix/cron-bsm-resolution-33465

Conversation

@trac3r00

Copy link
Copy Markdown

Replaces bare dotenv.load_dotenv() with load_hermes_dotenv() from hermes_cli.env_loader in cron/scheduler.py so Bitwarden Secrets Manager (BSM) credentials are resolved for cron jobs.

Root cause: run_job loaded env via plain python-dotenv, skipping the _apply_external_secret_sources path entirely. Any cron job needing a BSM-managed key failed with HTTP 401.

What changed:

  • cron/scheduler.py: 5-line bare dotenv block → single load_hermes_dotenv(hermes_home=_get_hermes_home()) call
  • Tests: updated all 19 dotenv.load_dotenv mocks across test_scheduler.py, test_cron_profile.py", test_cron_workdir.pyto mockcron.scheduler.load_hermes_dotenv` instead
  • Added test_cron_bsm_resolution.py regression test verifying the scheduler calls load_hermes_dotenv during run_job

Deduplication: _apply_external_secret_sources already guards by _APPLIED_HOMES (per commit de76f4d), so redundant BSM pulls on subsequent ticks are avoided.

Closes #33465

Replaces bare dotenv.load_dotenv() with load_hermes_dotenv() from
hermes_cli.env_loader so Bitwarden Secrets Manager (BSM) credentials
are pulled for cron jobs.

- load_hermes_dotenv uses _load_dotenv_with_fallback (same UTF-8/latin-1
  fallback as the old code) then calls _apply_external_secret_sources,
  which is already process-level deduplicated (de76f4d) so redundant
  BSM calls are avoided on subsequent ticks.

Closes #33465
@liuhao1024

Copy link
Copy Markdown
Contributor

I found one issue worth fixing before merge.

tests/cron/test_cron_bsm_resolution.py:27 — monkeypatch/import mismatch means the test doesn't actually verify BSM resolution.

The production code uses a local import inside _run_job_impl:

# cron/scheduler.py, inside _run_job_impl()
from hermes_cli.env_loader import load_hermes_dotenv
load_hermes_dotenv(hermes_home=_get_hermes_home())

But the test patches at the module level:

monkeypatch.setattr("cron.scheduler.load_hermes_dotenv", _fake_load_hermes_dotenv)

Because the import is local (inside the function body), Python resolves from hermes_cli.env_loader import load_hermes_dotenv at call time, reading directly from hermes_cli.env_loader — the module-level attribute set by monkeypatch is never consulted. The real load_hermes_dotenv runs instead of the fake.

The test passes by accident: the real function loads tmp_path/.env (which doesn't exist), so it's a no-op, and the assertion captured.get("called") is True silently fails... wait, it actually does fail. Let me re-check.

Actually, monkeypatch.setattr("cron.scheduler.load_hermes_dotenv", ...) sets cron.scheduler.load_hermes_dotenv as a module attribute. The local import from hermes_cli.env_loader import load_hermes_dotenv binds load_hermes_dotenv to a local variable inside the function. The module attribute is irrelevant — the local import shadows it.

The same pattern affects all test_scheduler.py patchespatch("cron.scheduler.load_hermes_dotenv") sets a module attribute that the local import never reads. These tests work because load_hermes_dotenv with an empty .env is a no-op, not because the mock intercepts it.

Suggested fix — either:

  1. Patch the source module: monkeypatch.setattr("hermes_cli.env_loader.load_hermes_dotenv", _fake_load_hermes_dotenv) — this intercepts the import wherever it happens.

  2. Or add a module-level import in cron/scheduler.py so the module attribute is actually used:

# At top of cron/scheduler.py
from hermes_cli.env_loader import load_hermes_dotenv

Then the patch("cron.scheduler.load_hermes_dotenv") pattern works because the local name load_hermes_dotenv is bound at import time to the module attribute.

Option (1) is the minimal change. Option (2) is more consistent with how test_scheduler.py patches other imports (like hermes_state.SessionDB).

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cron Cron scheduler and job management area/auth Authentication, OAuth, credential pools labels May 28, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competing fix for #33465. Also see PR #33479 (BSM-only fix) and PR #33527 (omnibus fix: BSM + ticker hardening + null-safe deliver). Maintainers should pick one.

@trac3r00 trac3r00 closed this by deleting the head repository May 28, 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/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: cron scheduler missing BSM resolution

4 participants