fix(windows-update): a user-launched hermes serve/dashboard is never a Desktop backend
Canonicalising _is_backend_argv onto _hermes_holder_subcommand dropped the '-m hermes_cli.main' entry-shape discriminator the old predicate had. That widened _orphaned_desktop_backend_pids to tree-kill a standalone hermes.exe serve / hermes dashboard whose console parent died. The Desktop's only spawn shape is -m hermes_cli.main (apps/desktop/electron/main.ts), so _is_backend_argv now forwards to _looks_like_desktop_control_plane — the same canonical-subcommand AND entry-shape predicate — instead of a second copy. Test matrix gains a 'Desktop backend?' column with plain hermes serve / hermes.exe dashboard rows.
This commit is contained in:
@@ -424,9 +424,14 @@ def _relaunch_stopped_serves(token: dict) -> None:
|
||||
|
||||
|
||||
def _is_backend_argv(argv_low: str) -> bool:
|
||||
"""Whether an argv is a Desktop backend: the canonical holder classifier says ``serve``/``dashboard``
|
||||
(a substring test matched ``kanban --preserve-cache`` and ``-m dashboard serve`` wrong, #91869)."""
|
||||
return _hermes_holder_subcommand(argv_low) in ("serve", "dashboard")
|
||||
"""Whether an argv is a DESKTOP backend — feeds ``taskkill /T`` via ``_orphaned_desktop_backend_pids``.
|
||||
|
||||
Same predicate as ``_looks_like_desktop_control_plane``: ``-m hermes_cli.main`` entry shape (the
|
||||
Desktop's only spawn shape, ``apps/desktop/electron/main.ts``) AND the canonical holder classifier says
|
||||
``serve``/``dashboard``. A user-launched ``hermes.exe serve`` / ``hermes dashboard`` is NOT the
|
||||
Desktop's: the guard refuses on it, never reaps it.
|
||||
"""
|
||||
return _looks_like_desktop_control_plane(argv_low)
|
||||
|
||||
|
||||
def _live_argv_low(psutil, pid, cmdline: str) -> str | None:
|
||||
|
||||
@@ -11,28 +11,34 @@ from hermes_cli.update_cmd_windows import _hermes_holder_subcommand, _is_backend
|
||||
|
||||
LOOPBACK = "--host 127.0.0.1 --port 0"
|
||||
|
||||
# (cmdline, holder subcommand, desktop-local reap?). Substring scanners get every "trap" row wrong:
|
||||
# "serve" appears inside --preserve-cache / observer.py / a flag value.
|
||||
# (cmdline, holder subcommand, desktop-local reap?, Windows updater "Desktop backend"?). Substring
|
||||
# scanners get every "trap" row wrong: "serve" appears inside --preserve-cache / observer.py / a flag
|
||||
# value. The updater's kill set additionally requires the Desktop's `-m hermes_cli.main` spawn shape —
|
||||
# a user-launched `hermes serve` / `hermes dashboard` is refused on, never tree-killed.
|
||||
CMDLINES = [
|
||||
("python -m hermes_cli.main serve " + LOOPBACK, "serve", True),
|
||||
("/venv/bin/hermes serve --isolated --host=127.0.0.1 --port=0 --ssh-owner-nonce abc", "serve", True),
|
||||
("hermes --profile ops serve " + LOOPBACK, "serve", True),
|
||||
("hermes -m serve kanban --preserve-cache " + LOOPBACK, "kanban", False),
|
||||
("python -m hermes_cli.main kanban --preserve-cache " + LOOPBACK, "kanban", False),
|
||||
("hermes --reasoning high dashboard " + LOOPBACK, "dashboard", False),
|
||||
("hermes gateway run --replace", "gateway", False),
|
||||
("hermes chat --model serve", "chat", False),
|
||||
("python observer.py serve " + LOOPBACK, None, False),
|
||||
("python -m hermes_cli.main serve " + LOOPBACK, "serve", True, True),
|
||||
("python -m hermes_cli.main dashboard", "dashboard", False, True),
|
||||
("/venv/bin/hermes serve --isolated --host=127.0.0.1 --port=0 --ssh-owner-nonce abc", "serve", True, False),
|
||||
(r"C:\hermes\.venv\Scripts\hermes.exe serve --host 100.106.105.2 --port 9119", "serve", False, False),
|
||||
("hermes.exe dashboard", "dashboard", False, False),
|
||||
("hermes --profile ops serve " + LOOPBACK, "serve", True, False),
|
||||
("hermes -m serve kanban --preserve-cache " + LOOPBACK, "kanban", False, False),
|
||||
("python -m hermes_cli.main kanban --preserve-cache " + LOOPBACK, "kanban", False, False),
|
||||
("hermes --reasoning high dashboard " + LOOPBACK, "dashboard", False, False),
|
||||
("hermes gateway run --replace", "gateway", False, False),
|
||||
("hermes chat --model serve", "chat", False, False),
|
||||
("python observer.py serve " + LOOPBACK, None, False, False),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cmdline,subcommand,reapable", CMDLINES)
|
||||
def test_kill_and_relaunch_predicates_agree_with_the_canonical_holder_matcher(cmdline, subcommand, reapable):
|
||||
@pytest.mark.parametrize("cmdline,subcommand,reapable,desktop_backend", CMDLINES)
|
||||
def test_kill_and_relaunch_predicates_agree_with_the_canonical_holder_matcher(
|
||||
cmdline, subcommand, reapable, desktop_backend):
|
||||
assert _hermes_holder_subcommand(cmdline) == subcommand
|
||||
# Desktop-local reap (a KILL path): serve + loopback + ephemeral port, decided by tokens.
|
||||
assert _is_desktop_local_serve_cmdline(cmdline) is reapable
|
||||
# Windows updater backend classifier (stop + relaunch path).
|
||||
assert _is_backend_argv(cmdline.lower()) is (subcommand in ("serve", "dashboard"))
|
||||
# Windows updater backend classifier (taskkill /T on orphans): canonical subcommand AND Desktop spawn shape.
|
||||
assert _is_backend_argv(cmdline.lower()) is desktop_backend
|
||||
|
||||
|
||||
def test_desktop_local_serve_spares_fixed_port_and_remote_hosts():
|
||||
|
||||
Reference in New Issue
Block a user