From cdcd6021ccfeb2bd36dd9219806838b40479bd19 Mon Sep 17 00:00:00 2001 From: Frank Song Date: Mon, 4 May 2026 16:28:33 +0800 Subject: [PATCH] fix(cron): tighten worker-side broad-except in _run_cron_tracked (closes #1578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the try/except Exception wrapper around cron_profile_context_for_home(...).__enter__() in _run_cron_tracked. A silent fallback to ctx=None would leave the worker thread unpinned against process-global HERMES_HOME, silently corrupting cross-profile state — the same class of bug as #1573. Add regression test to catch any future re-introduction. --- api/routes.py | 10 ++----- .../test_scheduled_jobs_profile_isolation.py | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/api/routes.py b/api/routes.py index 3c8b8086..95ca7c66 100644 --- a/api/routes.py +++ b/api/routes.py @@ -198,14 +198,10 @@ def _run_cron_tracked(job, profile_home=None): # (threads have no TLS, so get_active_hermes_home() can't resolve). ctx = None if profile_home is not None: - try: - from api.profiles import cron_profile_context_for_home + from api.profiles import cron_profile_context_for_home - ctx = cron_profile_context_for_home(profile_home) - ctx.__enter__() - except Exception: - logger.exception("Failed to pin profile %s for cron run", profile_home) - ctx = None + ctx = cron_profile_context_for_home(profile_home) + ctx.__enter__() try: success, output, final_response, error = run_job(job) diff --git a/tests/test_scheduled_jobs_profile_isolation.py b/tests/test_scheduled_jobs_profile_isolation.py index 973f62db..ec765c81 100644 --- a/tests/test_scheduled_jobs_profile_isolation.py +++ b/tests/test_scheduled_jobs_profile_isolation.py @@ -197,3 +197,33 @@ def test_cron_run_does_not_silently_swallow_profile_resolution_errors(): "HERMES_HOME. Let the exception propagate (500 the request) rather " "than corrupt cross-profile state silently." ) + + +def test_cron_worker_does_not_silently_fall_back_on_profile_context_failure(): + """_run_cron_tracked must NOT silently set ctx=None when + cron_profile_context_for_home(...).__enter__() raises. + + A silent fallback in the worker thread would leave the job running + unpinned against process-global HERMES_HOME, silently corrupting + cross-profile state — the same class of bug as #1573. We'd rather + let the exception propagate and kill the worker thread than risk + that. + + Source-level assertion to catch any future re-introduction of the + over-broad except clause around the context setup. + """ + from pathlib import Path + src = (Path(__file__).resolve().parent.parent / "api" / "routes.py").read_text(encoding="utf-8") + + idx = src.find("def _run_cron_tracked(job, profile_home=None):") + assert idx != -1, "_run_cron_tracked not found" + body = src[idx : idx + 2000] + + # The profile-context setup must NOT be wrapped in try/except that + # silently falls back to ctx=None. + assert "except Exception" not in body[:body.find("run_job(job)")], ( + "_run_cron_tracked silently falls back to ctx=None when " + "cron_profile_context_for_home(...).__enter__() raises. That leaves " + "the worker thread unpinned against process-global HERMES_HOME. " + "Let the exception propagate rather than corrupt cross-profile state." + )