fix(serve): restart-friendly exit after update stops a supervised backend - #69046
fix(serve): restart-friendly exit after update stops a supervised backend#69046bounce12340 wants to merge 1 commit into
Conversation
Related: #69029 and #39166 address the supervised-update restart family. This patch asks the supervisor to restart through an exit marker; #69029 restarts the detected service directly. The documented shared-marker race makes this a maintainer mechanism decision, not a duplicate. |
|
Mechanism comparison for the needs-decision call, since #69029 addresses the same failure from the other side:
Happy to adapt this PR either way once the mechanism is picked. |
…kend - hermes update (ZIP and git-pull paths) writes a restart marker before terminating serve/dashboard pids; `hermes serve --stop` does not. - serve/dashboard consumes its own fresh marker on graceful shutdown and exits 75 (EX_TEMPFAIL, same semantics as the gateway subsystem) so systemd Restart=on-failure brings it back; without a marker, or without a supervisor, behavior is unchanged. - stale-marker hygiene: startup clears leftovers (Windows taskkill /F skips graceful exit), entries expire after 10 minutes, corrupt files are discarded. - docs: recommend Restart=on-failure/always in the desktop systemd section. Fixes NousResearch#68934 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e0d284c to
a006117
Compare
|
Rebased onto current main, which now carries #39166's managed-service restart. The two compose rather than compete, so this PR is now scoped to the gap that one leaves behind.
if restart_managed and _restart_managed_dashboard_service(reason):
return # #39166 handled it via systemctl
...
if restart_hint:
write_restart_markers(pids) # only reached when it did notThat residual set is: a systemd unit under any other name (the report in #68934 describes
Affected suites: 32 passed, 9 skipped (the new skips are #39166's POSIX-only systemd tests on a Windows runner). Happy to narrow this further — or close it — if the remaining coverage isn't worth the extra surface now that the common case is handled. |
|
Closing in favor of PR #72192 (merged), which resolves #68934 from the updater side: it snapshots each killed PID's owning systemd unit from |
What does this PR do?
When a backend update is applied (including remotely from Desktop),
hermes updateterminateshermes servewith SIGTERM. Under systemdRestart=on-failure, a graceful SIGTERM exit counts as a clean stop, so the service is never restarted — Desktop then polls for ~60 s and reports "Backend did not come back online" (#68934).This PR makes the update-triggered stop restart-friendly without changing the deliberate "Hermes doesn't guess original launch args" design (
_kill_stale_dashboard_processesdocstring): instead of restarting anything itself, it lets the supervisor do it. The update path writes a restart marker before terminating serve/dashboard pids; on graceful shutdown the server consumes its own fresh marker and exits with code 75 (EX_TEMPFAIL — same semantics the gateway subsystem already uses ingateway/restart.py), whichRestart=on-failuretreats as a failure and restarts. No marker (normal shutdown,--stop, no supervisor) → behavior unchanged.Known limitation: if several supervised serve processes are killed by one update and restart concurrently, a sibling's startup cleanup can consume the shared marker first (the survivor then exits 0). Single-server setups — the normal case — are unaffected; noted in case maintainers want per-pid marker files instead.
Related Issue
Fixes #68934
Type of Change
Changes Made
hermes_cli/serve_restart_marker.py(new) — marker read/write/consume helpers,RESTART_EXIT_CODE = 75; marker lives at$HERMES_HOME/runtime/serve_restart.json(same runtime-state convention asactive_sessions.py), written with the existingatomic_json_write.hermes_cli/main.py—_kill_stale_dashboard_processes(restart_hint=...): writes markers before any termination signal (both the Windows taskkill and POSIX SIGTERM/SIGKILL branches). ZIP and git-pull update call sites passrestart_hint=True; the--stopcall site keeps the defaultFalseso a manual stop stays clean.hermes_cli/web_server.py— startup clears stale marker entries for a reborn pid (Windowstaskkill /Fskips graceful exit); after uvicorn returns, a consumed fresh marker exits 75 on both the POSIX and Windows runner paths.website/docs/user-guide/desktop.md— systemd section now recommendsRestart=on-failure/alwaysand documents the exit-75 contract.main),--stopwrites no marker, consume hit/mismatch/expiry/missing-file cases, exit-75 vs normal-exit paths, plus a class-local POSIXpsparser fixture for the pre-existingTestFindStaleDashboardPidsfailures on Windows runners (the Windowswmiccoverage is untouched).How to Test
python -m pytest tests/hermes_cli/test_update_stale_dashboard.py tests/hermes_cli/test_serve_command.py -q→ 32 passed, 5 skipped (POSIX-only kill tests on Windows).hermes_cli/main.py+hermes_cli/web_server.pyonly → the ordering test and exit-path tests fail; restore → green.hermes serveunder a unit withRestart=on-failure, apply a backend update from Desktop → service now restarts and Desktop reconnects;hermes serve --stopstill stops it for good.Checklist
Code
fix(scope):,feat(scope):, etc.)test_update_stale_dashboard.py,test_serve_command.py,test_web_server.py,test_dashboard_lifecycle_flags.py— all green); fullpytest tests/ -qon this machine stops at 12 pre-existing collection errors from missing optionalacpdeps, unrelated to this changeDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/A (no config keys added)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A (no architecture change)🤖 Generated with Claude Code