From 25641342da7a1c0531808d9e9ce2758f496bedab Mon Sep 17 00:00:00 2001 From: burak33bb Date: Tue, 4 Aug 2026 04:40:44 +0300 Subject: [PATCH] fix(update): classify long gateway command lines --- hermes_cli/_scan_venv_blockers.py | 35 ++++++++++++++++--- tests/hermes_cli/test_scan_venv_blockers.py | 37 ++++++++++++++++++++- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/hermes_cli/_scan_venv_blockers.py b/hermes_cli/_scan_venv_blockers.py index 9673ca1ac192..b4dc198a8ab2 100644 --- a/hermes_cli/_scan_venv_blockers.py +++ b/hermes_cli/_scan_venv_blockers.py @@ -126,6 +126,29 @@ def _is_pausable_gateway(cmdline: str) -> bool: return looks_like_gateway_command_line(cmdline) +def _is_pausable_gateway_process(pid: int, captured_cmdline: str) -> bool: + """Classify a holder using its live argv when the capture is incomplete. + + ``_detect_venv_python_processes`` caps captured command lines at 120 + characters for display. Managed-runtime interpreter paths can exceed that + limit before the trailing ``gateway run`` tokens, so the captured prefix + alone is not sufficient for the Desktop preflight exemption. Re-read the + argv only for classification and keep the captured prefix for output. + + If the process exits or cannot be inspected, fail closed by retaining the + captured-prefix verdict: the holder remains a blocker. + """ + if _is_pausable_gateway(captured_cmdline): + return True + try: + import psutil # noqa: PLC0415 + + live_cmdline = " ".join(psutil.Process(int(pid)).cmdline()) + except Exception: + return False + return bool(live_cmdline) and _is_pausable_gateway(live_cmdline) + + def main() -> None: """Entry point. Prints one JSON doc to stdout. Exits 0 for valid scan.""" try: @@ -140,6 +163,11 @@ def main() -> None: except Exception as exc: _emit_probe_fail(f"scan aborted: {exc}") + pausable_gateway_pids = { + pid + for pid, _name, cmdline in matches + if _is_pausable_gateway_process(pid, cmdline) + } processes = [ { "pid": pid, @@ -147,20 +175,19 @@ def main() -> None: "cmdline": _redact_sensitive_cmdline(cmdline), } for pid, name, cmdline in matches - if not _is_pausable_gateway(cmdline) + if pid not in pausable_gateway_pids ] - exempted = sum(1 for _pid, _name, cmdline in matches if _is_pausable_gateway(cmdline)) data = { "ok": True, "blocked": bool(processes), "processes": processes, # Diagnostic only: gateway processes present but not counted as # blockers because the downstream updater pauses them itself. - "pausable_gateways": exempted, + "pausable_gateways": len(pausable_gateway_pids), } print(json.dumps(data)) sys.exit(0) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/tests/hermes_cli/test_scan_venv_blockers.py b/tests/hermes_cli/test_scan_venv_blockers.py index 08142502d68c..65a98d6a65e5 100644 --- a/tests/hermes_cli/test_scan_venv_blockers.py +++ b/tests/hermes_cli/test_scan_venv_blockers.py @@ -211,4 +211,39 @@ def test_main_desktop_serve_backend_still_blocks(monkeypatch, capsys): assert code == 0 assert data["blocked"] is True assert [p["pid"] for p in data["processes"]] == [78] - assert data["pausable_gateways"] == 0 \ No newline at end of file + assert data["pausable_gateways"] == 0 + + +def test_main_classifies_gateway_from_live_cmdline_when_capture_is_truncated( + monkeypatch, capsys +): + """Long managed-runtime paths must not hide the trailing gateway command.""" + executable = ( + "C:/Users/u/.hermes/hermes-agent/.hermes-runtime/python/" + "generation-1785179197-21284-2ba3099e/" + "cpython-3.11-windows-x86_64-none/python.exe" + ) + argv = [executable, "-m", "hermes_cli.main", "gateway", "run"] + full_cmdline = " ".join(argv) + captured = full_cmdline[:120] + assert "gateway" not in captured + + process = types.SimpleNamespace(cmdline=lambda: argv) + fake_psutil = types.SimpleNamespace(Process=lambda pid: process) + monkeypatch.setitem(sys.modules, "psutil", fake_psutil) + import hermes_cli.main as cli_main + + monkeypatch.setattr( + cli_main, + "_detect_venv_python_processes", + lambda: [(42, "python.exe", captured)], + ) + + with pytest.raises(SystemExit) as excinfo: + main() + data = json.loads(capsys.readouterr().out) + + assert excinfo.value.code == 0 + assert data["blocked"] is False + assert data["processes"] == [] + assert data["pausable_gateways"] == 1