Upgrades from ≤v2026.9.14 keep a cached root utils without file_signature (narrow package-prefix purge). The post-pull gateway restart then imports fresh hermes_cli.config and dies with ImportError, failing the daily Install & Update E2E hermes-update legs. Drop the incomplete utils cache before that import so the one pre-handoff upgrade can complete. Co-authored-by: Samik Bandyopadhyay <callsamik@users.noreply.github.com>
46 lines
1.8 KiB
Python
46 lines
1.8 KiB
Python
"""Heal mixed ``sys.modules`` after an in-place checkout update.
|
|
|
|
Pre-reexec updaters (Hermes ≤ v2026.9.14) purged only package prefixes
|
|
(``hermes_cli``, ``gateway``, ``tools``, ``tui_gateway``, ``agent``) and left
|
|
root modules like ``utils`` cached in the updater process. The post-pull
|
|
gateway-restart phase then imports new ``hermes_cli.gateway`` /
|
|
``hermes_cli.config`` into that process; those need symbols the stale
|
|
``utils`` lacks (``file_signature``), and ``hermes update`` exits 1 with
|
|
``gateway auto-restart failed: cannot import name 'file_signature' from
|
|
'utils'``.
|
|
|
|
Post-swap hand-off (``hermes_cli.update_handoff``) makes this class dead for
|
|
updaters that already include it. This module is the bridge for the one
|
|
upgrade from a pre-handoff release onto a tree that needs new root symbols:
|
|
freshly imported ``hermes_cli`` code drops the incomplete cache before
|
|
importing ``utils``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from typing import Mapping, Sequence
|
|
|
|
# Root modules the narrow purge left behind, keyed by attributes that must
|
|
# exist on the on-disk copy after this release. Extend when a new root-level
|
|
# symbol would otherwise break the pre-handoff upgrade path.
|
|
_ROOT_MODULE_REQUIRED_ATTRS: dict[str, tuple[str, ...]] = {
|
|
"utils": ("file_signature",),
|
|
}
|
|
|
|
|
|
def drop_stale_root_modules(
|
|
required: Mapping[str, Sequence[str]] | None = None,
|
|
) -> list[str]:
|
|
"""Drop cached root modules missing required attrs. Returns dropped names."""
|
|
checks = _ROOT_MODULE_REQUIRED_ATTRS if required is None else required
|
|
dropped: list[str] = []
|
|
for name, attrs in checks.items():
|
|
mod = sys.modules.get(name)
|
|
if mod is None:
|
|
continue
|
|
if any(not hasattr(mod, attr) for attr in attrs):
|
|
sys.modules.pop(name, None)
|
|
dropped.append(name)
|
|
return dropped
|