From 5341f135a527f2e1ca42a2d011aab5ace3dc49ea Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:50:30 +0530 Subject: [PATCH] fix(update): the purge keeps hermes_constants; it is refreshed in place instead hermes_constants owns the _HERMES_HOME_OVERRIDE ContextVar. Evicting it hands later imports a fresh var: a reset token taken through the old module raises, and an override set before the purge silently disappears. _reload_updated_runtime_modules already re-executes it in place before every purge, so new symbols still arrive. Caught by tests/hermes_cli/test_update_config_reload_tools_config.py on the stack. --- hermes_cli/update_cmd_maint.py | 10 ++++++---- .../test_update_config_reload_tools_config.py | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hermes_cli/update_cmd_maint.py b/hermes_cli/update_cmd_maint.py index d5b3ea68dd..2413d96d2d 100644 --- a/hermes_cli/update_cmd_maint.py +++ b/hermes_cli/update_cmd_maint.py @@ -27,10 +27,12 @@ _UPDATE_RUNTIME_RELOAD_MODULES = "hermes_constants", "tools.environments.local", #: Modules EXECUTING the update survive the purge: evicting them buys nothing (running frames #: keep them alive) and reloading them mid-flight is the one genuinely unsafe move. -#: ``hermes_logging`` is protected for a different reason: its queue listener, handler list and -#: ``_logging_initialized`` flag are module globals, so a fresh copy starts a SECOND -#: QueueListener over the same log files while the first one keeps running. -_STALE_PURGE_PROTECTED = frozenset({"hermes_cli", "hermes_cli.main", "hermes_logging"}) +#: Two root modules carry process-wide identity state and are refreshed in place by +#: ``_reload_updated_runtime_modules`` instead: ``hermes_logging`` (a fresh copy starts a SECOND +#: QueueListener over the same log files while the first keeps running) and ``hermes_constants`` +#: (its ``_HERMES_HOME_OVERRIDE`` ContextVar — a token taken through the old module cannot reset a +#: fresh module's var, and an override set before the purge would silently vanish). +_STALE_PURGE_PROTECTED = frozenset({"hermes_cli", "hermes_cli.main", "hermes_logging", "hermes_constants"}) #: The updater's own module family (``update_cmd*``, ``update_receipt``, ``update_inventory``, #: ``update_lock``, ...) is protected as a prefix: these hold per-run state — the open receipt diff --git a/tests/hermes_cli/test_update_config_reload_tools_config.py b/tests/hermes_cli/test_update_config_reload_tools_config.py index 59842d7548..03feb631c1 100644 --- a/tests/hermes_cli/test_update_config_reload_tools_config.py +++ b/tests/hermes_cli/test_update_config_reload_tools_config.py @@ -70,9 +70,9 @@ def test_update_migration_survives_stale_module_missing_call_time_symbol(tmp_pat def test_update_migration_import_failure_after_purge_prints_fallback(tmp_path, monkeypatch, capsys): - """The purge evicts ``hermes_cli.*`` but not root modules (``hermes_constants``, ...), so the - post-purge ``from hermes_cli.config import ...`` re-executes NEW config.py against OLD root - modules and can raise ImportError. That must print the 'run hermes config migrate' fallback and + """The purge protects ``hermes_constants`` (identity-bearing ContextVar state), so the + post-purge ``from hermes_cli.config import ...`` re-executes NEW config.py against that OLD + root module and can raise ImportError. That must print the 'run hermes config migrate' fallback and return — not escape and abort the rest of post-update maintenance (fleet restart).""" home = tmp_path / "flat-home" home.mkdir()