fix(dashboard): list --port 0 serve backends in --status

--status probed the port parsed from argv, so a backend launched with
--port 0 (Desktop SSH backends) was always skipped while --stop still
killed it. Use the bind the spawn ledger records after listening.
This commit is contained in:
Hermes Agent
2026-09-24 23:10:15 -05:00
committed by brooklyn!
parent a79311ba95
commit 15e26c76f3
3 changed files with 49 additions and 2 deletions

View File

@@ -108,6 +108,23 @@ def _scan_dashboard_processes(*, exclude_pids: set[int] | None = None) -> list[t
return found
def _ledger_serve_binds() -> dict[int, tuple[str, int]]:
"""``pid -> (host, port)`` recorded in the spawn ledger for live serve/dashboard backends.
The entry is written after the bind, so it carries the real port where argv only says
``--port 0`` (Desktop SSH backends ask the OS for a port). Empty when the ledger is unavailable.
"""
binds: dict[int, tuple[str, int]] = {}
with contextlib.suppress(Exception):
from hermes_cli.process_identity import ledger_entries
for entry in ledger_entries():
pid, port = entry.get("pid"), entry.get("port")
if (entry.get("purpose") in ("serve", "dashboard") and isinstance(pid, int)
and isinstance(port, int) and port > 0):
binds[pid] = (str(entry.get("host") or ""), port)
return binds
def _pid_environ(pid: int) -> dict[str, str] | None:
"""Exec-time environment of *pid* (psutil, then /proc); ``None`` when unreadable."""
with contextlib.suppress(Exception):

View File

@@ -503,16 +503,21 @@ def _report_dashboard_status() -> int:
``--status`` let an operator kill what they couldn't see.
Ledger-registered serves (profiled launches the argv scan can't match) surface via the spawn-ledger
augmentation in _scan_dashboard_processes. See #81564.
augmentation in _scan_dashboard_processes, and the ledger's recorded bind replaces the argv port so
``--port 0`` backends are probed on the port the OS actually gave them. See #81564.
"""
from hermes_cli.dashboard_procs import _scan_dashboard_processes
from hermes_cli.dashboard_procs import _ledger_serve_binds, _scan_dashboard_processes
from gateway.status import _pid_exists
binds = _ledger_serve_binds()
live: list[tuple[int, str, str]] = []
for pid, command in _scan_dashboard_processes():
runtime = _parse_dashboard_runtime(command)
if runtime is None:
continue
mode, host, port = runtime
if pid in binds:
ledger_host, port = binds[pid]
host = ledger_host or host
if port <= 0 or not _pid_exists(pid) or not _dashboard_listening(host, port):
continue
live.append((pid, command, mode))

View File

@@ -56,6 +56,31 @@ class TestDashboardStatus:
assert "PID 12346" in out
assert "PID 12347" in out and "[serve]" in out
def test_status_lists_os_assigned_port_serve_that_stop_targets(self, capsys, monkeypatch):
"""A ``--port 0`` serve (Desktop SSH backend) is listed on the port the ledger recorded,
so ``--status`` shows every backend ``--stop`` would kill (#81564)."""
import socket
from hermes_cli import dashboard_procs, process_identity
from hermes_cli.main_dashboard import _find_stale_dashboard_pids
pid = 424242
with socket.socket() as listener:
listener.bind(("127.0.0.1", 0))
listener.listen()
real_port = listener.getsockname()[1]
monkeypatch.setattr(dashboard_procs, "_iter_process_table", lambda: [
(pid, "/usr/local/bin/hermes serve --host 127.0.0.1 --port 0 --ssh-isolated")])
monkeypatch.setattr(process_identity, "ledger_entries", lambda: [
{"pid": pid, "purpose": "serve", "host": "127.0.0.1", "port": real_port}])
monkeypatch.setattr("gateway.status._pid_exists", lambda p: p == pid)
assert _find_stale_dashboard_pids() == [pid]
with pytest.raises(SystemExit) as exc:
cmd_dashboard(_ns(status=True))
assert exc.value.code == 0
assert f"PID {pid} [serve]" in capsys.readouterr().out
def test_status_does_not_try_to_import_fastapi(self):
"""`--status` must not require dashboard runtime deps — it's a
process-table scan only. We prove this by making fastapi import