fix(cron): reset cron-delivery ContextVars to _UNSET after a job - #47089
fix(cron): reset cron-delivery ContextVars to _UNSET after a job#47089aldoeliacim wants to merge 1 commit into
Conversation
|
Related: #43370 (cron session-context isolation for the approval-mode/HERMES_CRON_SESSION vars) and #8866 (cron delivery-target reliability). Same _UNSET-vs-explicit-empty ContextVar bug class as the merged #17747 defensive clearing, applied to the auto-delivery path. Not a duplicate -- different vars, complementary fix. |
9c76a8a to
8f91e1d
Compare
8f91e1d to
f1d2ae0
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes a cron scheduler ContextVar lifecycle bug where post-job cleanup reset the cron auto-delivery ContextVars to "" (explicitly-cleared), unintentionally suppressing get_session_env’s os.environ fallback for later callers running in the same thread/task. The change restores the correct “never set” sentinel (_UNSET) after each job, preventing cross-run and cross-test pollution.
Changes:
- Add
reset_cron_delivery_vars()ingateway/session_context.pyto restoreHERMES_CRON_AUTO_DELIVER_*ContextVars to_UNSET. - Update
cron/scheduler.py::run_job()to callreset_cron_delivery_vars()infinallyinstead of setting those ContextVars to"". - Add regression tests in
tests/gateway/test_session_env.pyverifying_UNSETrestoration andos.environfallback behavior after reset.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
gateway/session_context.py |
Adds a dedicated reset helper to restore cron auto-delivery ContextVars to _UNSET (re-enabling env fallback). |
cron/scheduler.py |
Switches post-job cleanup to use the new reset helper to avoid leaking "" into later callers. |
tests/gateway/test_session_env.py |
Adds regression coverage for the sentinel vs empty-string behavior and env fallback restoration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Use ContextVars for per-job session/delivery state so parallel jobs | ||
| # don't clobber each other's targets (os.environ is process-global). | ||
| from gateway.session_context import set_session_vars, clear_session_vars, _VAR_MAP | ||
| from gateway.session_context import set_session_vars, clear_session_vars, reset_cron_delivery_vars, _VAR_MAP | ||
|
|
| _CRON_DELIVERY_VARS = ( | ||
| "HERMES_CRON_AUTO_DELIVER_PLATFORM", | ||
| "HERMES_CRON_AUTO_DELIVER_CHAT_ID", | ||
| "HERMES_CRON_AUTO_DELIVER_THREAD_ID", | ||
| ) |
b771187 to
42e9530
Compare
run_job() sets the HERMES_CRON_AUTO_DELIVER_* ContextVars while a job runs so
the job's own send_message can detect a duplicate auto-delivery target. The
cleanup in the finally block set them back to "" — but "" is the
"explicitly cleared" state that suppresses the os.environ fallback in
get_session_env, NOT the "never set" (_UNSET) sentinel. So after any cron run,
every later caller in the same thread/task reads an empty cron-delivery value
instead of falling back to os.environ.
In a long-lived scheduler process this lets a finished cron job shadow a later
same-thread caller's auto-delivery env. It also surfaces as cross-test
pollution: after tests/cron/test_scheduler.py runs, the duplicate-target
detection in tests/tools/test_send_message_tool.py reads the leaked empty
ContextVar instead of the env the test set.
Fix: add session_context.reset_cron_delivery_vars(), which restores the three
cron-delivery vars to _UNSET, and call it in run_job's finally cleanup instead
of set(""). The per-job set("") at job START is unchanged (correct: each job
starts from a clean explicitly-empty state before its target is resolved).
Adds regression tests in tests/gateway/test_session_env.py asserting the vars
return to _UNSET and that get_session_env's os.environ fallback works again
after a reset.
Related: #43370 (fixes the sibling HERMES_CRON_SESSION leak via the token
mechanism; this covers the auto-delivery vars it does not touch), #8866.
42e9530 to
1d8a94a
Compare
What & why
run_job()sets theHERMES_CRON_AUTO_DELIVER_*ContextVars while a job runsso the job's own
send_messagecan detect a duplicate auto-delivery target.The cleanup in the
finallyblock reset them withvar.set("").The problem: in
gateway/session_context.py,""is the explicitly-clearedstate that intentionally suppresses the
os.environfallback inget_session_env— it is not the same as the_UNSET"never set" sentinel.So once a cron job finishes, the three auto-delivery vars stay at
""for thelifetime of that thread/task, and every later caller in the same process reads
an empty cron-delivery value instead of falling back to
os.environ.Consequences:
shadow a later same-thread caller's auto-delivery environment.
tests/cron/test_scheduler.pyruns, the duplicate-target detection intests/tools/test_send_message_tool.py::test_cron_duplicate_target_is_skipped_and_explainedfails with
KeyError: 'skipped'because it reads the leaked empty ContextVarinstead of the env it set. The test passes in isolation, which is the
classic ordering-dependent symptom.
The fix
session_context.reset_cron_delivery_vars(), which restores the threeHERMES_CRON_AUTO_DELIVER_*vars to the_UNSETsentinel (re-enabling theos.environfallback for later callers).run_job()'sfinallycleanup instead of theset("")loop.The per-job
set("")at job start is deliberately unchanged: each jobshould begin from a clean explicitly-empty state before its delivery target is
resolved, so a job that resolves no target can't inherit the previous job's.
Only the post-job cleanup is corrected.
How to test
New regression tests in
tests/gateway/test_session_env.pyassert thatreset_cron_delivery_vars()returns the vars to_UNSETand thatget_session_env'sos.environfallback works again after a reset.Local run:
tests/cron/test_scheduler.py(136) +tests/gateway/test_session_env.py(15, incl. 2 new) + the previously order-dependent send_message test all green
(152 passed together).
Platforms tested
Linux (Python 3.11). The change is pure ContextVar lifecycle management with no
OS-specific behavior.
Related
HERMES_CRON_SESSIONapproval-mode leak via theset_session_vars/token mechanism. That PR does not touch theauto-delivery vars; this is the same bug class for the delivery-target path.
Rebased onto current
main(2026-06-26). Conflict intests/gateway/test_session_env.py: main added new executor tests (..._survives_default_executor_shutdown,..._refuses_resurrection_after_shutdown) in the same region as this PR'sreset_cron_delivery_varstests; resolved by keeping both blocks.cron/scheduler.py+gateway/session_context.pyauto-merged clean. Verified: session_env + cron suites = 594 passed.