fix(desktop): keep serve backend alive through Windows launcher

This commit is contained in:
Math 2026-08-10 19:44:40 -05:00 committed by Teknium
parent 6a20383eeb
commit 1edfdeee81
2 changed files with 41 additions and 8 deletions

View File

@ -17696,9 +17696,26 @@ def _maybe_open_browser(
threading.Thread(target=_open, daemon=True).start()
def _is_serve_orphaned(original_ppid: int, getppid=os.getppid) -> bool:
"""True when this process lost its original spawning parent (ppid changed)."""
return getppid() != original_ppid
def _is_serve_orphaned(desktop_pid: int, pid_exists=None) -> bool:
"""True when the Desktop process that owns this serve backend is gone.
``HERMES_PARENT_PID`` is the Electron Desktop PID, not necessarily this
Python process's immediate PPID. On Windows the venv ``hermes.exe`` launcher
introduces one or more shim processes, so comparing ``os.getppid()`` to the
Electron PID incorrectly treats a healthy backend as orphaned and exits 0.
Probe the recorded Desktop PID directly instead.
Any liveness-probe failure is fail-safe: keep serving rather than killing a
backend whose owner could not be conclusively shown to be dead.
"""
try:
if pid_exists is None:
from gateway.status import _pid_exists
pid_exists = _pid_exists
return not bool(pid_exists(int(desktop_pid)))
except Exception:
return False
def _start_parent_death_watchdog() -> None:
@ -17716,7 +17733,7 @@ def _start_parent_death_watchdog() -> None:
if not raw:
return
try:
original_ppid = int(raw)
desktop_pid = int(raw)
except (TypeError, ValueError):
return
try:
@ -17725,7 +17742,7 @@ def _start_parent_death_watchdog() -> None:
poll = 2.0
def _loop() -> None:
while not _is_serve_orphaned(original_ppid):
while not _is_serve_orphaned(desktop_pid):
time.sleep(poll)
os._exit(0)
@ -17733,9 +17750,8 @@ def _start_parent_death_watchdog() -> None:
def _demo() -> None:
# orphan iff current ppid differs from the recorded spawning parent
assert _is_serve_orphaned(999999999, getppid=lambda: 1) is True
assert _is_serve_orphaned(42, getppid=lambda: 42) is False
assert _is_serve_orphaned(999999999, pid_exists=lambda _pid: False) is True
assert _is_serve_orphaned(42, pid_exists=lambda _pid: True) is False
print("web_server parent-death watchdog self-check: OK")

View File

@ -0,0 +1,17 @@
"""Regression tests for Desktop-owned ``hermes serve`` lifecycle tracking."""
from hermes_cli.web_server import _is_serve_orphaned
def test_parent_watchdog_tracks_recorded_desktop_pid_not_immediate_ppid():
"""Windows venv launch shims must not make a live Desktop look orphaned."""
assert _is_serve_orphaned(4242, pid_exists=lambda pid: pid == 4242) is False
assert _is_serve_orphaned(4242, pid_exists=lambda _pid: False) is True
def test_parent_watchdog_fails_safe_when_liveness_probe_errors():
def broken_probe(_pid: int) -> bool:
raise OSError("process table temporarily unavailable")
assert _is_serve_orphaned(4242, pid_exists=broken_probe) is False