Skip to content

feat(update): add --stop-services to stop and relaunch this install's dashboard/serve processes - #70742

Open
shaase-ctrl wants to merge 1 commit into
NousResearch:mainfrom
shaase-ctrl:feat/update-stop-services
Open

feat(update): add --stop-services to stop and relaunch this install's dashboard/serve processes#70742
shaase-ctrl wants to merge 1 commit into
NousResearch:mainfrom
shaase-ctrl:feat/update-stop-services

Conversation

@shaase-ctrl

Copy link
Copy Markdown

What does this PR do?

On Windows, hermes update already 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). 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-services that closes exactly that gap: before the dependency sync it stops hermes dashboard / hermes serve processes 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 an atexit safety net for error paths, mirroring the existing gateway pause/resume design.

Safety properties:

  • The guard stays authoritative. Its re-scan runs unchanged after the stop; foreign holders — desktop backends, other installs, anything the stop could not kill — still refuse with exit 2, and the services we did stop are relaunched before exiting.
  • Never touches processes hermes doesn't own: desktop-app-managed backends (HERMES_DESKTOP_CHILD_PID), systemd-managed dashboard units (left to _restart_managed_dashboard_service), other installs' processes.
  • Byte-for-byte identical without the flag (default off, no config key).
  • Success-tail relaunch is ordered after the stale-dashboard sweep so the sweep cannot re-kill the fresh processes (regression-tested).

Known residual (documented in the help text): if the updater itself is force-killed (SIGKILL/power loss), atexit cannot 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

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • hermes_cli/subcommands/update.py — add --stop-services flag
  • hermes_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_impl
  • tests/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 flag

How to Test

  1. Start a dashboard from the install venv, e.g. pythonw.exe -m hermes_cli.main dashboard --host 0.0.0.0 (or via a Scheduled Task)
  2. Run hermes update --stop-services --backup --yes
  3. The update stops the dashboard, completes, and relaunches it with the same argv; without the flag the venv guard refuses as before
  4. Unit: pytest tests/hermes_cli/test_update_stop_services.py -q → 27 passed

Checklist

Code

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — updating.md, cli-commands.md, docstrings
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no config keys added)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — feature works on POSIX too (stop uses the existing SIGTERM→SIGKILL path, relaunch uses start_new_session); Windows specifics behind _is_windows()/sys.platform exactly like the surrounding code
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

$ hermes update --stop-services --backup --yes
⚕ Updating Hermes Agent...
→ Stopping Windows gateway process(es) before updating Hermes...
  ✓ Paused gateway profile(s): default
→ Stopping Hermes service process(es) before updating (--stop-services)...
  → hermes dashboard (PID 47772)
→ Fetching updates...
...
✓ Update complete!
  ✓ Restarting Windows gateway profile(s): default
→ Restarting Hermes service process(es) in the background...
  ✓ Restarted hermes dashboard (was PID 47772)
$ pytest tests/hermes_cli/test_update_stop_services.py -q
27 passed in 1.12s

🤖 Generated with Claude Code

… 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>
@shaase-ctrl
shaase-ctrl marked this pull request as ready for review July 24, 2026 11:54
@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard area/install-update Installer, updater, packaging, wheels, doctor platform/windows Native Windows-specific behavior or breakage P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 24, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-11057 before reaching _stop_hermes_services_for_update() at :11078. A dashboard launched through the normal hermes.exe shim therefore still blocks the flag.
  • The candidate match at hermes_cli/main.py:10765-10767 only accepts literals such as hermes dashboard; it does not accept the Windows hermes.exe dashboard / hermes.exe serve argv forms that the earlier guard detects.
  • tests/hermes_cli/test_update_stop_services.py:587-596 uses inspect.getsource; replace this source-shape assertion with a call-order behavior test.

Suggested changes

  • Salvage the feature into the current hermes_cli/update_cmd.py and hermes_cli/dashboard_procs.py split. Current main already owns the adjacent post-update respawn flow in dashboard_procs.py:213-327; retain that path and add the Windows pre-guard stop/recheck behavior there.

Automated hermes-sweeper review.

Comment thread hermes_cli/main.py
# 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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread hermes_cli/main.py
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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added the sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users label Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have platform/windows Native Windows-specific behavior or breakage sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants