From edc695d8e0f4f25d5726e0b0223661dd2a67b183 Mon Sep 17 00:00:00 2001 From: Robert J Samson Date: Thu, 16 Apr 2026 20:34:53 -0400 Subject: [PATCH] fix: use macOS-compatible ps args in gateway process-table scan macOS ps rejects the BSD `e` flag in `ps -A eww`, so whenever /proc is unavailable (always true on macOS) _scan_gateway_pids() silently found nothing and gateway/cron status reported a live gateway as down. - select `ps -Aww -o pid=,command=` on macOS; keep `ps -A eww` on Linux - add regression tests for both argv selections with /proc forced unavailable The launchctl plist-parsing half of the original PR landed separately on main (_parse_launchd_pid_from_list_output), so this branch now carries only the process-scan fix, per review feedback. Co-Authored-By: Claude Fable 5 --- hermes_cli/gateway.py | 13 ++++++- tests/hermes_cli/test_gateway_service.py | 49 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index ce60bfb2ccdf..030c417ca7bc 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -475,7 +475,7 @@ def _matches_gateway_runtime(command: str) -> bool: current_cmd = "" else: # Try /proc first (works in Docker without procps installed), - # fall back to ps -A eww. + # fall back to a platform-appropriate ps invocation. _found_via_proc = False if os.path.isdir("/proc"): try: @@ -501,8 +501,17 @@ def _matches_gateway_runtime(command: str) -> bool: pass if not _found_via_proc: + # macOS `ps` doesn't accept the BSD `e` flag the way Linux does, + # so we can't surface env vars there. As a result, gateways started + # on macOS with only `HERMES_HOME=` (no `--profile` flag) + # won't be attributable to a named profile via `_matches_current_profile`. + ps_command = ( + ["ps", "-Aww", "-o", "pid=,command="] + if is_macos() + else ["ps", "-A", "eww", "-o", "pid=,command="] + ) result = subprocess.run( - ["ps", "-A", "eww", "-o", "pid=,command="], + ps_command, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=10, diff --git a/tests/hermes_cli/test_gateway_service.py b/tests/hermes_cli/test_gateway_service.py index 7a7911687fd0..9d1c37427832 100644 --- a/tests/hermes_cli/test_gateway_service.py +++ b/tests/hermes_cli/test_gateway_service.py @@ -1551,6 +1551,55 @@ def fake_run(*args, **kwargs): assert gateway_cli._is_service_running() is False +class TestScanGatewayPidsPsSelection: + """The ps fallback in _scan_gateway_pids must use macOS-valid arguments. + + macOS `ps` rejects the BSD `e` flag in the `-A eww` invocation, so when + /proc is unavailable (always true on macOS) the scan silently found + nothing and gateway/cron status reported a live gateway as down. + """ + + def _force_ps_fallback(self, monkeypatch, macos, home): + monkeypatch.setattr(gateway_cli, "is_macos", lambda: macos) + monkeypatch.setattr(gateway_cli, "is_windows", lambda: False) + monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: Path(home)) + monkeypatch.setattr(gateway_cli, "_get_ancestor_pids", lambda: set()) + + real_isdir = os.path.isdir + monkeypatch.setattr( + gateway_cli.os.path, + "isdir", + lambda path: False if path == "/proc" else real_isdir(path), + ) + + calls = [] + + def fake_run(cmd, capture_output=True, text=True, timeout=10, **kwargs): + calls.append(cmd) + return SimpleNamespace( + returncode=0, + stdout=( + f"12345 {home}/hermes-agent/venv/bin/python" + " -m hermes_cli.main gateway run --replace\n" + ), + stderr="", + ) + + monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run) + return calls + + def test_scan_uses_macos_ps_args_when_proc_unavailable(self, monkeypatch): + calls = self._force_ps_fallback(monkeypatch, macos=True, home="/Users/test/.hermes") + + assert gateway_cli._scan_gateway_pids(set()) == [12345] + assert calls == [["ps", "-Aww", "-o", "pid=,command="]] + + def test_scan_keeps_linux_ps_args_when_proc_unavailable(self, monkeypatch): + calls = self._force_ps_fallback(monkeypatch, macos=False, home="/home/test/.hermes") + + assert gateway_cli._scan_gateway_pids(set()) == [12345] + assert calls == [["ps", "-A", "eww", "-o", "pid=,command="]] + class TestGatewaySystemServiceRouting: def test_systemd_restart_gracefully_restarts_running_service_and_waits(self, monkeypatch, capsys): calls = []