fix(update): reload the cached tools_config before post-pull migrations

The updater process is the pre-pull process: hermes_cli.tools_config cached in sys.modules lacks symbols the pull added, so a migration that imports them at call time fails with ImportError and the config silently stays at the old version (#111271).
This commit is contained in:
liuhao1024
2026-09-15 06:47:09 +08:00
committed by Teknium
parent e43f2f6816
commit 91a3ab4c85
2 changed files with 60 additions and 3 deletions
+5 -3
View File
@@ -16,12 +16,14 @@ def _reload_config_modules() -> None:
"""Force-reload config modules after git pull: the updater is the PRE-pull process, so the
cached modules hold OLD code and ``check_config_version()`` would report "up to date" despite a
pulled migration. ``_subprocess_compat`` / ``dashboard_procs`` reload too so the later dashboard
cleanup sees symbols the update added."""
cleanup sees symbols the update added. ``tools_config`` reloads as well: migrations import its
helpers at call time (``_migrate_to_45`` needs ``_configurable_keys``), and a pre-pull cache
lacks symbols the pull added (#111271)."""
import importlib
importlib.invalidate_caches()
for mod_name in (
"hermes_cli.config_defaults", "hermes_cli.config", "hermes_cli.config_migrations",
"hermes_cli._subprocess_compat", "hermes_cli.dashboard_procs"):
"hermes_cli.config_defaults", "hermes_cli.config", "hermes_cli.tools_config",
"hermes_cli.config_migrations", "hermes_cli._subprocess_compat", "hermes_cli.dashboard_procs"):
mod = sys.modules.get(mod_name)
if mod is not None:
try:
@@ -0,0 +1,55 @@
"""Regression tests for the ``tools_config`` leg of the post-update config reload.
``hermes update`` runs config migrations in the updater process — the PRE-pull
process — after refreshing the config modules from the pulled tree. Migrations
import ``hermes_cli.tools_config`` helpers at call time, so a ``tools_config``
cached in ``sys.modules`` from before the pull makes the import fail with
``ImportError`` even though the symbol exists in the freshly-written file.
History:
- #111271: the v45 migration (``_migrate_to_45``) imports ``_configurable_keys``
from ``hermes_cli.tools_config``; on a v0.20.6 → v0.21.3 update the cached
pre-pull module lacked that newly-added symbol, the migration aborted with
"cannot import name '_configurable_keys'", and the config silently stayed at
the old version while the code and gateway moved on (update exit 1).
"""
from __future__ import annotations
import importlib
import sys
def test_reload_config_modules_restores_missing_tools_config_symbol():
"""A pre-pull ``tools_config`` cache must be reloaded before migrations run.
Simulates the stale updater process by deleting the migration-imported
symbol from the already-loaded module, then asserts ``_reload_config_modules``
re-executes the on-disk code and restores it.
"""
tools_config = sys.modules.get("hermes_cli.tools_config")
if tools_config is None:
import hermes_cli.tools_config as tools_config # noqa: F811
assert hasattr(tools_config, "_configurable_keys")
del tools_config._configurable_keys
try:
from hermes_cli.update_cmd_config import _reload_config_modules
_reload_config_modules()
assert hasattr(sys.modules["hermes_cli.tools_config"], "_configurable_keys")
finally:
reloaded = sys.modules["hermes_cli.tools_config"]
if not hasattr(reloaded, "_configurable_keys"):
importlib.reload(reloaded) # keep other tests on a clean module
def test_reload_config_modules_is_noop_without_cached_tools_config():
"""The reload must stay safe when ``tools_config`` was never imported."""
from hermes_cli.update_cmd_config import _reload_config_modules
was_loaded = sys.modules.pop("hermes_cli.tools_config", None)
try:
_reload_config_modules()
finally:
if was_loaded is not None:
sys.modules["hermes_cli.tools_config"] = was_loaded
# No assertion on the module here: the point is that the helper did not raise.