fix(update_stage): look the shim marker up in the process home, not ~/.hermes

update_stage rebuilt the home from HERMES_HOME with a literal ~/.hermes
fallback while update_lock resolves the same marker through
get_process_hermes_home(). Where the platform default differs (sudo invoker,
data-dir suffix) the two disagreed and the old-shim UI fallback found no
marker, leaving the hand-off window frozen. hermes_constants is stdlib-only,
so the lazy import keeps this module usable under -I -S -B.
This commit is contained in:
ethernet
2026-09-21 19:32:37 -04:00
parent d500d05184
commit d1b7d5689f
2 changed files with 28 additions and 4 deletions

View File

@@ -33,6 +33,14 @@ UI_SPAWNED_ENV = "HERMES_UPDATE_UI_ACTIVE"
MARKER_NAME = ".hermes-update-in-progress"
def _process_home() -> Path:
"""The marker and the shim's log live in the PROCESS home (update_lock.update_marker_path):
the shim resolved ``$HERMES_HOME`` or the platform default, never a profile override, and
the platform default (sudo invoker, data-dir suffix) is not ``~/.hermes`` everywhere."""
from hermes_constants import get_process_hermes_home
return get_process_hermes_home()
def status_file() -> Path | None:
"""The watching shim's status file, or None when no UI is discoverable."""
exported = os.environ.get(STATUS_FILE_ENV, "").strip()
@@ -49,9 +57,8 @@ def _status_from_marker() -> Path | None:
shim's status file lives beside it, pid-suffixed. No marker (plain CLI
update) or no matching file → no UI.
"""
home = os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes")
try:
pid = int((Path(home) / MARKER_NAME).read_text(encoding="utf-8-sig")
pid = int((_process_home() / MARKER_NAME).read_text(encoding="utf-8-sig")
.splitlines()[0].strip())
except (OSError, ValueError, IndexError):
return None
@@ -113,8 +120,7 @@ def ensure_panel(update_root: Path) -> None:
if not panel.is_file():
return
try:
home = os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes")
log = Path(home) / "logs" / "desktop-update-handoff.log"
log = _process_home() / "logs" / "desktop-update-handoff.log"
if log.is_file() and _ui_present_in_log(log.read_text(encoding="utf-8-sig", errors="replace")[-8000:]):
return
import subprocess

View File

@@ -67,6 +67,24 @@ def test_marker_fallback_covers_the_old_shim(status_file, tmp_path, monkeypatch)
"Updating Python dependencies (PM)")
def test_marker_fallback_uses_the_platform_home_without_env_var(status_file, tmp_path, monkeypatch):
"""The shim without HERMES_HOME resolved the platform default, which is not ~/.hermes
on every host (sudo invoker, data-dir suffix); the marker must be looked up there."""
import hermes_constants
monkeypatch.delenv("HERMES_HOME", raising=False)
monkeypatch.setattr(hermes_constants, "_get_platform_default_hermes_home", lambda: tmp_path / "platform")
(tmp_path / "platform").mkdir()
(tmp_path / "platform" / update_stage.MARKER_NAME).write_text(
f"80335\n{int(time.time())}\n", encoding="utf-8")
status_file.write_text('{"status":"running","message":"old"}', encoding="utf-8")
monkeypatch.setenv("TMPDIR", str(tmp_path))
update_stage.publish_stage("Building products")
_assert_running(status_file.read_text(encoding="utf-8"), "Building products")
def test_no_ui_sources_is_inert(status_file, tmp_path, monkeypatch):
"""A plain CLI update has no env var and no marker: publish must no-op."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path)) # no marker inside