From a827aae5f61a1159eeac89bcc9be56d23f6d8393 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 8 Aug 2026 18:50:02 -0700 Subject: [PATCH] =?UTF-8?q?fix(update):=20don't=20truncate=20cmdlines=20in?= =?UTF-8?q?=20the=20venv-blocker=20scan=20=E2=80=94=20it=20broke=20the=20g?= =?UTF-8?q?ateway=20exemption?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _detect_venv_python_processes() returned cmdline_raw[:120]. Gateways autostarted via the managed-runtime interpreter carry a >120-char exe path (.hermes-runtime\python\generation-...\cpython-3.11-...), so the truncated cmdline ended inside the exe path, before '-m hermes_cli.main gateway run'. The Desktop preflight's pausable-gateway exemption (_scan_venv_blockers._is_pausable_gateway) therefore never matched, the gateway was reported as a blocker, and every Desktop update aborted with 'Update didn't finish' even with all windows closed — the updater's own gateway pause never got a chance to run. Fix: return the full cmdline from the detector and truncate only at display time (_format_venv_python_holders_message and the scan's JSON cmdline field, after redaction). Reproduced live on Windows 11: scan reported blocked=true for '...cpython-3.1' (truncated); after the fix the same gateway pair scans clear with pausable_gateways=2. --- hermes_cli/_scan_venv_blockers.py | 5 +++- hermes_cli/update_cmd.py | 10 +++++-- tests/hermes_cli/test_scan_venv_blockers.py | 33 ++++++++++++++++++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/hermes_cli/_scan_venv_blockers.py b/hermes_cli/_scan_venv_blockers.py index 9673ca1ac192e..8a0fe153c66dd 100644 --- a/hermes_cli/_scan_venv_blockers.py +++ b/hermes_cli/_scan_venv_blockers.py @@ -144,7 +144,10 @@ def main() -> None: { "pid": pid, "name": name, - "cmdline": _redact_sensitive_cmdline(cmdline), + # Truncate for display AFTER the gateway exemption has seen the + # full cmdline (long managed-runtime interpreter paths would + # otherwise swallow the `gateway run` argv). + "cmdline": _redact_sensitive_cmdline(cmdline)[:120], } for pid, name, cmdline in matches if not _is_pausable_gateway(cmdline) diff --git a/hermes_cli/update_cmd.py b/hermes_cli/update_cmd.py index 45099198bb2dd..4f9729752f07a 100644 --- a/hermes_cli/update_cmd.py +++ b/hermes_cli/update_cmd.py @@ -2926,7 +2926,13 @@ def _detect_venv_python_processes( if not is_holder: continue name = info.get("name") or Path(exe).name - matches.append((int(pid), str(name), cmdline_raw[:120])) + # Return the FULL cmdline: callers match against it (the Desktop + # preflight's pausable-gateway exemption parses for `gateway run`). + # Truncating here cut long managed-runtime interpreter paths before + # the `-m hermes_cli.main gateway run` argv, so autostarted gateways + # were misreported as blockers and the update dead-ended. Truncate + # only at display time. + matches.append((int(pid), str(name), cmdline_raw)) return matches def _format_venv_python_holders_message(matches: list[tuple[int, str, str]]) -> str: @@ -2941,7 +2947,7 @@ def _format_venv_python_holders_message(matches: list[tuple[int, str, str]]) -> hint = " ← Hermes Desktop backend (close the desktop app)" elif "gateway" in low: hint = " ← gateway" - lines.append(f" PID {pid} {name} {cmdline}{hint}") + lines.append(f" PID {pid} {name} {cmdline[:120]}{hint}") if len(matches) > 6: lines.append(f" ... and {len(matches) - 6} more") lines.append("") diff --git a/tests/hermes_cli/test_scan_venv_blockers.py b/tests/hermes_cli/test_scan_venv_blockers.py index 08142502d68c9..5eeeedb9ed241 100644 --- a/tests/hermes_cli/test_scan_venv_blockers.py +++ b/tests/hermes_cli/test_scan_venv_blockers.py @@ -211,4 +211,35 @@ 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_gateway_with_long_managed_runtime_path_is_exempt(monkeypatch, capsys): + """Regression: the detector must hand the FULL cmdline to the exemption. + + Gateways launched via the managed-runtime interpreter carry a >120-char + exe path (`.hermes-runtime\python\generation-...\cpython-3.11-...`). + The old `cmdline_raw[:120]` truncation in the detector cut the cmdline + before `-m hermes_cli.main gateway run`, so the exemption never matched + and every Desktop update aborted with 'Update didn't finish'. + Here the detector returns full cmdlines (post-fix contract); the scan + must exempt the gateway and truncate only the *displayed* cmdline. + """ + long_exe = ( + r'"C:\Users\u\AppData\Local\hermes\hermes-agent\.hermes-runtime\python' + r"\generation-1785095035-66720-be29ea9c\cpython-3.11-windows-x86_64-none" + r'\python.exe"' + ) + assert len(long_exe) > 120 # the truncation point was inside the exe path + gateway = (91, "python.exe", long_exe + " -m hermes_cli.main gateway run --replace") + code, data = _run_main_with_detector(monkeypatch, capsys, [gateway]) + assert code == 0 + assert data["blocked"] is False + assert data["processes"] == [] + assert data["pausable_gateways"] == 1 + + # A long-path NON-gateway holder still blocks, with cmdline truncated for display. + stray = (92, "python.exe", long_exe + " -m some_other_module --serve-forever") + code, data = _run_main_with_detector(monkeypatch, capsys, [gateway, stray]) + assert data["blocked"] is True + assert [p["pid"] for p in data["processes"]] == [92] + assert len(data["processes"][0]["cmdline"]) <= 120