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"