feat(update): add --stop-services to stop and relaunch this install's dashboard/serve processes - #70742
feat(update): add --stop-services to stop and relaunch this install's dashboard/serve processes#70742shaase-ctrl wants to merge 1 commit into
Conversation
… dashboard/serve processes On Windows, `hermes update` pauses gateways before the dependency sync, but a running dashboard or headless `hermes serve` backend still dead-ends the update at the venv-process guard (exit 2), and stale dashboards were only killed after the update with a manual-restart hint because their launch args were unknown (NousResearch#40449). With the opt-in flag, the update: - stops dashboard/serve processes that belong to THIS install (service cmdline patterns + the venv guard's ownership predicate), recording each one's PID, argv, and cwd; never touches desktop-app-managed backends (HERMES_DESKTOP_CHILD_PID), a systemd-managed dashboard unit, or other installs' processes, - re-runs the venv guard unchanged: foreign holders (and anything the stop could not kill) still refuse with exit 2 - after relaunching the services that were stopped, - relaunches the recorded services detached on every outcome (success, guard refusal, ZIP fallback, zero-commit return) with an atexit safety net, placed after the stale-dashboard sweep so the sweep cannot re-kill the fresh processes. Without the flag the behavior is byte-for-byte unchanged. Shared logic is extracted without behavior change (_HERMES_SERVICE_CMDLINE_PATTERNS, _desktop_child_pids, _terminate_service_pids). Refs NousResearch#40449 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing the Windows update dead-end; current main still has the venv-holder refusal in hermes_cli/update_cmd.py:3127-3132, so the underlying need remains.
Problems
- The stop call is placed after the shim guard: this PR exits at
hermes_cli/main.py:11051-11057before reaching_stop_hermes_services_for_update()at:11078. A dashboard launched through the normalhermes.exeshim therefore still blocks the flag. - The candidate match at
hermes_cli/main.py:10765-10767only accepts literals such ashermes dashboard; it does not accept the Windowshermes.exe dashboard/hermes.exe serveargv forms that the earlier guard detects. tests/hermes_cli/test_update_stop_services.py:587-596usesinspect.getsource; replace this source-shape assertion with a call-order behavior test.
Suggested changes
- Salvage the feature into the current
hermes_cli/update_cmd.pyandhermes_cli/dashboard_procs.pysplit. Current main already owns the adjacent post-update respawn flow indashboard_procs.py:213-327; retain that path and add the Windows pre-guard stop/recheck behavior there.
Automated hermes-sweeper review.
| # and record how to relaunch them. Registered with atexit AFTER the | ||
| # gateway resume above so the LIFO exit order restarts services first — | ||
| # the reverse of the stop order. | ||
| _services_resume = _stop_hermes_services_for_update(args) |
There was a problem hiding this comment.
This runs after the Windows shim guard at lines 11051-11057, which exits on another hermes.exe before this helper is reached. Move candidate stopping before that guard and re-run the guard after the selected PIDs exit; otherwise a normally launched hermes.exe dashboard still cannot use this flag.
| continue | ||
| argv = list(info.get("cmdline") or []) | ||
| cmdline_raw = " ".join(argv) | ||
| if not any(p in cmdline_raw for p in _HERMES_SERVICE_CMDLINE_PATTERNS): |
There was a problem hiding this comment.
The raw-argv patterns do not include hermes.exe dashboard or hermes.exe serve. On Windows the normal launcher path is an executable argv[0], so this can fail to identify the exact process blocked by the earlier shim guard. Match executable-based forms and cover them in a test.
| """Ordering contract: the success-tail relaunch of --stop-services | ||
| services must come AFTER _kill_stale_dashboard_processes(restart_managed= | ||
| True), or the sweep would re-kill the freshly respawned services.""" | ||
| src = inspect.getsource(cli_main._cmd_update_impl) |
There was a problem hiding this comment.
Please replace this source-text ordering assertion with a behavior test that records the cleanup and relaunch callbacks. Source inspection is brittle and is invalidated by the current update-pipeline extraction to hermes_cli/update_cmd.py.
What does this PR do?
On Windows,
hermes updatealready pauses gateways before the dependency sync, but a running dashboard or headlesshermes servebackend still dead-ends the update at the venv-process guard (exit 2). The user has to hunt the process down, kill it, update, and restart it by hand — every update. Post-update, stale dashboards are killed but never relaunched because "we don't know the original launch args" (#40449).This adds an opt-in
hermes update --stop-servicesthat closes exactly that gap: before the dependency sync it stopshermes dashboard/hermes serveprocesses belonging to this install (service cmdline patterns intersected with the venv guard's ownership predicate), records each one's PID, argv, and cwd, and relaunches them detached once the update finishes — on every outcome (success, guard refusal, ZIP fallback, zero-commit return), with anatexitsafety net for error paths, mirroring the existing gateway pause/resume design.Safety properties:
HERMES_DESKTOP_CHILD_PID), systemd-managed dashboard units (left to_restart_managed_dashboard_service), other installs' processes.Known residual (documented in the help text): if the updater itself is force-killed (SIGKILL/power loss),
atexitcannot run and services stay down — the same property the gateway pause has. On a failed Node refresh the relaunched service may serve a stale JS bundle, the same exposure a manual restart had.Scope note / related PRs: #64386 (
--force-kill) and #67229 (auto-terminate strays on--yes) terminate holders but do not record-and-restore them; #61515 reaps gateway children; #40616 restarts systemd dashboard units post-update. This PR is the complementary record-and-restore contract for manually-launched dashboard/serve processes and does not change any of their code paths.Related Issue
Refs #40449 (the manual-dashboard half; the systemd-messaging half is #40616's territory)
Type of Change
Changes Made
hermes_cli/subcommands/update.py— add--stop-servicesflaghermes_cli/main.py— new_stop_hermes_services_for_update,_restart_hermes_services_after_update,_spawn_detached_service,_dashboard_service_main_pid; behavior-preserving extractions_HERMES_SERVICE_CMDLINE_PATTERNS,_desktop_child_pids,_terminate_service_pids; five call-site insertions in_cmd_update_impltests/hermes_cli/test_update_stop_services.py— 27 tests (parser, detection/ownership/exclusions, stop exit-wait + survivors, relaunch idempotency + PID-reuse guard, guard-refusal/ZIP/atexit wiring, no-flag byte-identical output, success-tail ordering)website/docs/reference/cli-commands.md,website/docs/getting-started/updating.md— document the flagHow to Test
pythonw.exe -m hermes_cli.main dashboard --host 0.0.0.0(or via a Scheduled Task)hermes update --stop-services --backup --yespytest tests/hermes_cli/test_update_stop_services.py -q→ 27 passedChecklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) —updating.md,cli-commands.md, docstringscli-config.yaml.exampleif I added/changed config keys — N/A (no config keys added)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Astart_new_session); Windows specifics behind_is_windows()/sys.platformexactly like the surrounding codeScreenshots / Logs
🤖 Generated with Claude Code