Conversation
On Desktop serve startup, reap orphan gateway processes (PPID=1) left behind by a previous serve session that exited abnormally. This prevents the old and new gateways from racing for the same QQ WebSocket credential, which splits messages across parallel session trees (NousResearch#77276).
Same lifecycle idea, serve-shaped cousinThis PR reaps orphan gateways at startup — we hit the same pattern for orphan Desktop-local Related:
complementary, not competing — gateways and serves both need the “reap already-orphaned + don’t leave new orphans” pair. |
|
Merged via PR #83406 — your commits were cherry-picked onto current main with your authorship preserved in git log (rebase merge). Thank you for the contribution! The omnibus combined the WAL read-connection pool (#76700), the configurable nofile floor (#77587), the parent-death watchdog (#73066), the Desktop-boot orphan serve reap incl. the backend.lock.json spare-guard (#78873), and the orphan gateway reap (#78312). All pieces were live-tested end-to-end: real orphaned processes reaped on actual Desktop boot with a lock-owned backend surviving, 151→9 connections under 150 reader threads, and zero fd growth over 800 requests against a live serve. |
Fixes #77276.
🎯Problem
The Desktop backend forks a gateway child on every serve, but only graceful shutdown reaps it. On abnormal exit (crash / SIGKILL / power loss / forced update), the old gateway is reparented to launchd (PPID=1) and keeps holding the QQ WebSocket. The next serve forks a new gateway, and both race the same QQ credential — messages split across parallel session trees.
🎯Fix
_reap_unsupervised_gateway_orphans() (in hermes_cli/gateway.py) already handles exactly this case, but was only wired into the CLI stop path. This PR calls it once at Desktop lifespan startup, before the new gateway is forked — so stale orphans are cleared first.
Desktop-only (guarded by HERMES_DESKTOP=1)
Wrapped in try/except — a reap failure never blocks startup
Lazy import, matching the file's existing style
Reaper itself unchanged (SIGTERM → 5s → SIGKILL, current-profile match, no-op under systemd)
Net ~5 lines — a wiring fix reusing existing code, no new mechanism.
Relationship to #77297
Orthogonal: #77297 covers graceful shutdown, this covers all abnormal-exit paths. Merges independently.
Testing
Added test_desktop_lifespan_reaps_orphan_gateways_on_startup asserting the reaper runs once at startup under HERMES_DESKTOP=1. Existing reaper tests unchanged and green; full dashboard suite (39 tests) passes.