fix(update): don't truncate cmdlines in the venv-blocker scan — it broke the gateway exemption

_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.
This commit is contained in:
Teknium
2026-08-08 18:50:02 -07:00
parent b79e83827d
commit 0b33ee88e4
3 changed files with 44 additions and 4 deletions
+4 -1
View File
@@ -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)
+8 -2
View File
@@ -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("")
+32 -1
View File
@@ -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
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