fix(cron): run jobs under the profile secret scope

Once profile isolation is active (multiple gateway profiles or room->profile
multiplexing), get_secret() fails closed outside an installed scope. The cron
ticker fires jobs from a thread with no per-turn scope, so run_job() died in
resolve_runtime_provider() with UnscopedSecretError (e.g. for
OPENROUTER_BASE_URL / CUSTOM_BASE_URL) before model selection - every cron
job failed while interactive turns worked fine.

Wrap run_job() in set_secret_scope(build_profile_secret_scope(...)) with a
finally-reset, mirroring the proven per-turn pattern in gateway/run.py
(_profile_runtime_scope). Single-profile installs are unaffected (the scope
is just the profile's own .env).

tests/cron: 611 passed, 1 pre-existing unrelated failure
(TestRoutingIntents::test_all_token_case_insensitive fails identically on
unmodified main in a full-suite run and passes in isolation).
This commit is contained in:
Jonny Kovacs 2026-07-03 06:49:03 -05:00 committed by kshitij
parent 104232979d
commit fdab380a1a

View file

@ -3114,7 +3114,26 @@ def run_one_job(job: dict, *, adapters=None, loop=None, verbose: bool = False) -
) )
return True # not an error — already handled/removed return True # not an error — already handled/removed
success, output, final_response, error = run_job(job) # Run the job under the profile's secret scope. get_secret() fails
# closed outside a scope once profile isolation is in play (multiple
# gateway profiles / room→profile multiplexing), and cron fires from
# the ticker thread where no per-turn scope is installed — so
# resolve_runtime_provider() raised UnscopedSecretError before model
# selection, breaking every cron job. Mirrors the per-turn pattern in
# gateway/run.py (_profile_runtime_scope).
from agent.secret_scope import (
build_profile_secret_scope,
reset_secret_scope,
set_secret_scope,
)
_scope_token = set_secret_scope(
build_profile_secret_scope(_get_hermes_home())
)
try:
success, output, final_response, error = run_job(job)
finally:
reset_secret_scope(_scope_token)
output_file = save_job_output(job["id"], output) output_file = save_job_output(job["id"], output)
if verbose: if verbose: