From c51c0a31b1f7561b243a3f1edd04a4a3a46b0659 Mon Sep 17 00:00:00 2001 From: konsisumer Date: Sun, 31 May 2026 20:55:47 +0200 Subject: [PATCH] fix(gateway): decode schtasks output defensively on Windows hermes gateway status/restart spawned a UnicodeDecodeError traceback in subprocess's reader thread when schtasks emitted localized (cp936/GBK) console bytes under a UTF-8 ambient locale (git-bash/MSYS). Pin the decode to encoding=utf-8, errors=replace so the command stays quiet, matching the pattern already used by find_gateway_pids(). Refs #34083 --- hermes_cli/gateway_windows.py | 10 +++++++ tests/hermes_cli/test_gateway_windows.py | 33 +++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/hermes_cli/gateway_windows.py b/hermes_cli/gateway_windows.py index a7f4b983dcbf..d6905052d5e8 100644 --- a/hermes_cli/gateway_windows.py +++ b/hermes_cli/gateway_windows.py @@ -112,6 +112,16 @@ def _exec_schtasks(args: list[str]) -> tuple[int, str, str]: [schtasks, *args], capture_output=True, text=True, + # schtasks emits text in the console's localized code page (e.g. + # cp936/GBK on Chinese Windows). When the ambient locale resolves + # to UTF-8 — common under git-bash/MSYS — the default strict decode + # raises UnicodeDecodeError inside subprocess's reader thread and + # spams a traceback even though the command succeeded (issue + # #34083). Decode defensively, matching find_gateway_pids() in + # gateway.py. The ASCII keys we parse (status, last run result) + # survive; only localized free-text becomes U+FFFD. + encoding="utf-8", + errors="replace", timeout=_SCHTASKS_TIMEOUT_S, # CREATE_NO_WINDOW avoids a flashing console window when the CLI # is itself hosted in a TUI. See tools/browser_tool.py for the diff --git a/tests/hermes_cli/test_gateway_windows.py b/tests/hermes_cli/test_gateway_windows.py index e61302198282..ec407f97c277 100644 --- a/tests/hermes_cli/test_gateway_windows.py +++ b/tests/hermes_cli/test_gateway_windows.py @@ -1,6 +1,7 @@ """Tests for hermes_cli.gateway_windows.""" from pathlib import Path +from types import SimpleNamespace import pytest @@ -698,4 +699,34 @@ def fake_write(target_pid): monkeypatch.setattr(status_mod, "_pid_exists", lambda check_pid: False) # Returns True because _pid_exists immediately says "gone". - assert gateway_windows._drain_gateway_pid(pid, drain_timeout=5.0) is True \ No newline at end of file + assert gateway_windows._drain_gateway_pid(pid, drain_timeout=5.0) is True + + +def test_exec_schtasks_decodes_defensively(monkeypatch): + """schtasks output must decode without raising on non-UTF-8 console bytes. + + On Chinese Windows the schtasks code page is cp936/GBK; under a UTF-8 + ambient locale (git-bash/MSYS) the default strict decode raises + UnicodeDecodeError inside subprocess's reader thread, spamming a + traceback on an otherwise-successful ``hermes gateway status`` (#34083). + Pin the decode to errors="replace" so the call stays quiet. + """ + captured = {} + + def fake_run(cmd, **kwargs): + captured.update(kwargs) + return SimpleNamespace(returncode=0, stdout="Status: Running", stderr="") + + monkeypatch.setattr(gateway_windows, "_assert_windows", lambda: None) + monkeypatch.setattr( + gateway_windows.shutil, "which", + lambda name: r"C:\Windows\System32\schtasks.exe", + ) + monkeypatch.setattr(gateway_windows.subprocess, "run", fake_run) + + code, out, err = gateway_windows._exec_schtasks(["/Query", "/TN", "Hermes_Gateway"]) + + assert code == 0 + assert out == "Status: Running" + assert captured.get("encoding") == "utf-8" + assert captured.get("errors") == "replace"