fix(update): pending fleet restart leaves gateways already on the checkout code alone

The catch-up restart stopped every gateway before re-checking whether any was actually on
pre-update code, so the gateway the same `hermes update` had just cold-started was killed
and (on Windows) the stop/start pair printed "No gateway was running" followed by a second
spawn. _run_pending_fleet_restart now skips when the fleet probe shows every live gateway
current on the checkout SHA (identity known; any stale/down/unknown row still restarts).
The Windows direct-spawn report no longer prints the PID twice ("(PID n) (PID: n)").

Part of #117051
This commit is contained in:
teknium1 authored and Teknium committed 2026-09-20 18:52:45 -07:00
1 parent eaef5ec717
commit d6b0d37ece
3 files changed
+50 -2

No files matched your search

+2 -2
View File
@@ -806,7 +806,7 @@ def _start_or_report_running(running_pids: list[int] | None = None) -> None:
_report_already_running(running_pids)
else:
pid = _spawn_detached()
_report_gateway_start(f"direct spawn (PID {pid})")
_report_gateway_start("direct spawn")
def _install_startup_fallback(script_path: Path, start_now: bool, detail: str) -> None:
@@ -1610,7 +1610,7 @@ def start() -> None:
# Manual starts use the same console-less direct spawn as restart() and install --start-now;
# Scheduled Task / Startup entries are only login persistence.
pid = _spawn_detached()
_report_gateway_start(f"direct spawn (PID {pid})")
_report_gateway_start("direct spawn")
def _drain_gateway_pid(pid: int, drain_timeout: float) -> bool:
+25
View File
@@ -446,6 +446,23 @@ def _restart_systemd_gateway_units_best_effort(failed: list, listings) -> None:
failed.extend(f"systemd-{scope} (listing unavailable)" for scope, _ in _SYSTEMD_SCOPES if scope not in answered)
def _live_fleet_all_current() -> bool:
"""True when the fleet probe finds at least one gateway and every row is ``current`` at the
checkout SHA (identity known). Any unknown/stale/down row or a failed probe -> False (restart)."""
checkout_sha = _current_checkout_sha()
if not checkout_sha:
return False
try:
from hermes_cli.update_receipt import collect_fleet_versions
fleet = collect_fleet_versions()
except Exception as exc:
logger.debug("Pending fleet restart: fleet probe failed: %s", exc)
return False
if not fleet or _fleet_covered_gateways(fleet) is None:
return False
return all(row.get("state") == "current" and str(row.get("code_sha")) == checkout_sha for row in fleet)
def _run_pending_fleet_restart() -> bool:
"""Catch-up restart for gateways left on pre-update code. Never raises.
@@ -474,6 +491,14 @@ def _run_pending_fleet_restart() -> bool:
logger.debug("Pending fleet restart: gateway probe failed: %s", exc)
pids = None
# A gateway this very update cold-started (or a manual `hermes gateway restart` seconds
# ago) already serves the checkout code; stopping it here re-kills the fleet, and on
# Windows the stop/start pair then reports "No gateway was running" plus a second spawn
# (#117051). Skip when EVERY live gateway is current on the checkout SHA.
if pids and _live_fleet_all_current():
print(" ✓ Every running gateway already serves the checkout code — nothing to restart.")
return True
failed: list = []
try:
# Snapshot before stopping: Restart=no units can disappear from list-units on a clean exit.
@@ -362,6 +362,29 @@ def test_run_pending_restart_true_when_no_gateways(monkeypatch, capsys):
assert "Pending fleet restart completed" in capsys.readouterr().out
def test_run_pending_restart_skips_gateways_already_on_checkout_code(monkeypatch, capsys):
"""A gateway the update itself cold-started seconds earlier is already current on the checkout
SHA: the catch-up must not stop it (the Windows stop/start pair then printed "No gateway was
running" plus a second spawn, #117051) and must report nothing to restart."""
sha = "c" * 40
monkeypatch.setattr(update_cmd_fleet, "_current_checkout_sha", lambda: sha)
monkeypatch.setattr("hermes_cli.gateway.find_gateway_pids", lambda **k: [48096])
monkeypatch.setattr(
"hermes_cli.update_receipt.collect_fleet_versions",
lambda: [{"profile": "default", "pid": 48096, "code_sha": sha, "state": "current"}],
)
stopped = []
monkeypatch.setattr("hermes_cli.gateway.kill_gateway_processes", lambda **k: stopped.append(k))
monkeypatch.setattr("hermes_cli.gateway_windows.restart", lambda: stopped.append("windows-restart"))
monkeypatch.setattr("hermes_cli.gateway_windows.is_installed", lambda: True)
monkeypatch.setattr(update_cmd_fleet, "_restart_macos_launchd_gateways", lambda *a, **k: None)
monkeypatch.setattr(update_cmd_fleet, "_systemd_gateway_unit_listings", lambda: [])
assert update_cmd._run_pending_fleet_restart() is True
assert stopped == []
assert "nothing to restart" in capsys.readouterr().out
# ---------------------------------------------------------------------------
# cmd_update integration (mocked git / restart)
# ---------------------------------------------------------------------------