Windows subprocess Unicode encoding errors and fixes
NousResearch/hermes-agent · cluster 17049 · acp
Data as of
Nothing open on GitHub. Fix merged in #17435. Check it covers your case.
The short answer
- Known?
- Yes. 47 related items grouped here, 18 issues already closed.
- Fix event
- A fix was merged to main in PR #17435. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #17435, #38334, #70850, #70875. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and pull requests concern UnicodeDecodeError crashes on Windows when subprocess output uses non-UTF-8 locale encodings (e.g., cp1251, GBK). The members propose various fixes including explicit UTF-8 encoding, errors='replace' fallbacks, and environment variable overrides across multiple components.
Open issues0
Open PRs0
Fixes on record
-
#17435 fix(windows): decode wmic output with explicit encoding to survive non-UTF-8 locales (#17049)
files:
hermes_cli/gateway.py,hermes_cli/main.py,scripts/release.py,tests/hermes_cli/test_update_stale_dashboard.pyShow the diff
-
hermes_cli/gateway.py@@ -279,9 +279,11 @@ def _matches_current_profile(command: str) -> bool: ["wmic", "process", "get", "ProcessId,CommandLine", "/FORMAT:LIST"], capture_output=True, text=True, + encoding="utf-8", + errors="ignore", timeout=10, ) - if result.returncode != 0: + if result.returncode != 0 or result.stdout is None: return [] current_cmd = "" for line in result.stdout.split("\n"): -
hermes_cli/main.py@@ -5274,12 +5274,20 @@ def _warn_stale_dashboard_processes() -> None: try: if sys.platform == "win32": + # wmic may emit text in the system code page (for example cp936 + # on zh-CN systems), not UTF-8. In text mode, subprocess output + # decoding depends on Python's configuration (locale-dependent + # by default, or UTF-8 in UTF-8 mode). The important protection + # here is errors="ignore": it prevents a reader-thread + # UnicodeDecodeError from leaving result.stdout=None and turning + # the later .split() into an AttributeError (#17049). result = subprocess.run( ["wmic", "process", "get", "ProcessId,CommandLine", "/FORMAT:LIST"], capture_output=True, text=True, timeout=10, + encoding="utf-8", errors="ignore", ) - if result.returncode != 0: + if result.returncode != 0 or result.stdout is None: return current_cmd = "" for line in result.stdout.split("\n"): -
scripts/release.py@@ -263,6 +263,7 @@ "[redacted:email]": "Bartok9", "[redacted:email]": "CruxExperts", "[redacted:email]": "LeonSGP43", + "[redacted:email]": "Kailigithub", "[redacted:email]": "vlwkaos", "[redacted:email]": "Tranquil-Flow", "[redacted:email]": "mcndjxlefnd", -
tests/hermes_cli/test_update_stale_dashboard.py@@ -175,3 +175,55 @@ def test_grep_lines_ignored(self, capsys): output = capsys.readouterr().out assert "PID 99999" not in output assert "PID 12345" in output + + +class TestWindowsWmicEncoding: + """Regression tests for #17049 — the Windows wmic branch must not crash + `hermes update` on non-UTF-8 system locales (e.g. cp936 on zh-CN). + """ + + def test_wmic_invoked_with_utf8_ignore_errors(self, monkeypatch): + """The wmic subprocess.run call must pass encoding='utf-8' and + errors='ignore' so the subprocess reader thread cannot raise + UnicodeDecodeError on non-UTF-8 wmic output.""" + monkeypatch.setattr(sys, "platform", "win32") + with patch("subprocess.run") as mock_run: + # Provide a minimal valid wmic /FORMAT:LIST response. + mock_run.return_value = MagicMock( + returncode=0, + stdout=( + "CommandLine=python -m hermes_cli.main dashboard\n" + "ProcessId=12345\n" + ), + stderr="", + ) + _warn_stale_dashboard_processes() + + assert mock_run.called, "subprocess.run was not invoked" + kwargs = mock_run.call_args.kwargs + assert kwargs.get("encoding") == "utf-8", ( + "encoding kwarg must be 'utf-8' so wmic output is decoded " + "deterministically rather than via the implicit reader-thread " + "default that crashes on non-UTF-8 locales (#17049)." + ) + assert kwargs.get("errors") == "ignore", ( + "errors kwarg must be 'ignore' so undecodable bytes don't take " + "down the reader thread (#17049)." + ) + + def test_wmic_returns_none_stdout_does_not_crash(self, monkeypatch, capsys): + """If subprocess.run returns successfully but stdout is None — which + is what Python 3.11 leaves behind when the reader thread silently + crashed on UnicodeDecodeError before this fix landed — the warning + must short-circuit instead of raising AttributeError on + ``None.split('\\n')`` and aborting `hermes update` (#17049).""" + monkeypatch.setattr(sys, "platform", "win32") + with patch("subprocess.run") as mock_run: + mock_run.return_value = MagicMock( + returncode=0, stdout=None, stderr="" + ) + # Must not raise. + _warn_stale_dashboard_processes() + + output = capsys.readouterr().out + assert "dashboard process" not in output
-
-
#38334 fix(gateway): decode schtasks output with locale encoding on Windows (#38172)
files:
hermes_cli/gateway_windows.py,tests/hermes_cli/test_gateway_windows.pyShow the diff
-
hermes_cli/gateway_windows.py@@ -29,6 +29,7 @@ from __future__ import annotations import ctypes +import locale import os import re import shlex @@ -52,6 +53,20 @@ _TASK_DESCRIPTION = "Hermes Agent Gateway - Messaging Platform Integration" +def _schtasks_encoding() -> str: + """Best-effort console encoding for decoding ``schtasks.exe`` output. + + On localized Windows (e.g. Chinese), ``schtasks`` emits text in the OEM/ANSI + code page rather than UTF-8. Decoding with the wrong codec raised + ``UnicodeDecodeError`` inside ``subprocess``' reader threads. Prefer the + locale's preferred encoding and fall back to UTF-8. + """ + try: + return locale.getpreferredencoding(False) or "utf-8" + except Exception: + return "utf-8" + + # --------------------------------------------------------------------------- # Platform guard # --------------------------------------------------------------------------- @@ -112,6 +127,12 @@ def _exec_schtasks(args: list[str]) -> tuple[int, str, str]: [schtasks, *args], capture_output=True, text=True, + # Localized Windows emits schtasks output in the console code page, + # not UTF-8. Decode with the locale encoding and replace undecodable + # bytes so a non-UTF-8 status line never surfaces a UnicodeDecodeError + # traceback from subprocess' reader threads (issue #38172). + encoding=_schtasks_encoding(), + 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 -
tests/hermes_cli/test_gateway_windows.py@@ -29,6 +29,49 @@ def test_schtasks_fallback_does_not_hide_unknown_errors(): assert gateway_windows._should_fall_back(1, "ERROR: The system cannot find the file specified.") is False +def test_schtasks_encoding_falls_back_to_utf8(monkeypatch): + """A broken/empty locale must not leave us without a decoder (issue #38172).""" + + monkeypatch.setattr(gateway_windows.locale, "getpreferredencoding", lambda *a, **k: "") + assert gateway_windows._schtasks_encoding() == "utf-8" + + def _boom(*args, **kwargs): + raise RuntimeError("locale exploded") + + monkeypatch.setattr(gateway_windows.locale, "getpreferredencoding", _boom) + assert gateway_windows._schtasks_encoding() == "utf-8" + + +def test_exec_schtasks_decodes_with_replace_errors(monkeypatch): + """schtasks output must be decoded with errors='replace' so localized + (non-UTF-8) bytes never surface a UnicodeDecodeError traceback (#38172).""" + + captured: dict[str, object] = {} + + class _FakeCompleted: + returncode = 0 + stdout = "ok" + stderr = "" + + def fake_run(cmd, **kwargs): + captured["cmd"] = cmd + captured.update(kwargs) + return _FakeCompleted() + + 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, out, err) == (0, "ok", "") + assert captured["errors"] == "replace", "schtasks output must decode with errors='replace'" + assert isinstance(captured["encoding"], str) and captured["encoding"], ( + "an explicit non-empty encoding must be passed to subprocess.run" + ) + assert captured["text"] is True + + def test_build_gateway_argv_uses_base_pythonw_for_uv_venv_launcher(monkeypatch, tmp_path): """Avoid uv's venv pythonw launcher because it respawns console python.exe."""
-
-
#70850 fix(windows): utf-8 subprocess decode across the desktop backend — salvage #61978 + widen to sibling sites
files:
hermes_cli/banner.py,hermes_cli/web_server.py,tests/tui_gateway/test_subprocess_encoding.py,tui_gateway/compute_host.py,tui_gateway/host_supervisor.py,tui_gateway/server.py -
#70875 fix(windows): close out the text-mode subprocess decode bug class — salvage #55339/#60741/#60751 + full sweep + linter
files:
agent/anthropic_adapter.py,agent/context_references.py,agent/copilot_acp_client.py,agent/lsp/install.py,agent/proxy_sources/iron_proxy.py,agent/secret_sources/base.py,agent/secret_sources/bitwarden.py,agent/shell_hooks.py,agent/skill_preprocessing.py,agent/trace_upload.py,agent/transports/codex_app_server.py,batch_runner.py,cli.py,contributors/emails/[redacted:email],contributors/emails/[redacted:email],gateway/platforms/base.py,gateway/platforms/webhook.py,gateway/platforms/webhook_filters.py,gateway/run.py,gateway/shutdown_forensics.py(+72 more) -
#71014 fix(windows): residual encoding gaps — MCP stdio, gateway update file-I/O, STT/TTS, desktop spawn, bootstrap ver-stub
files:
apps/desktop/electron/backend-env.test.ts,apps/desktop/electron/backend-env.ts,gateway/dead_targets.py,gateway/delivery.py,gateway/platforms/qqbot/adapter.py,gateway/run.py,gateway/slash_commands.py,gateway/status.py,hermes_bootstrap.py,plugins/platforms/discord/adapter.py,plugins/platforms/feishu/adapter.py,plugins/platforms/google_chat/adapter.py,plugins/platforms/google_chat/oauth.py,plugins/platforms/telegram/adapter.py,plugins/platforms/whatsapp/adapter.py,tests/gateway/test_gateway_utf8_encoding.py,tests/test_hermes_bootstrap.py,tests/tools/test_mcp_stdio_encoding_handler.py,tools/mcp_tool.py,tools/transcription_tools.py(+1 more)
24 PRs closed without merging
From the close comments: merged via another change 7 · superseded by another PR 6 · done elsewhere 5 · duplicate 2 · other 4
-
#17073 fix(gateway): handle wmic UnicodeDecodeError gracefully · duplicate →
#17074,#17049 -
#17074 Fix UnicodeDecodeError in Windows wmic process scanning #17049 · done elsewhere →
cf83982,#17049 -
#17123 fix(update): protect dashboard wmic scan against UnicodeDecodeError on Windows non-UTF-8 locales (#17049) · merged via another change →
#17435 -
#17252 fix(gateway): handle wmic encoding errors on Windows non-English locales · merged via another change →
#17435 -
#17271 fix(gateway): add errors='replace' to subprocess.run for Windows wmic compatibility · superseded by another PR →
#17435,#17049
Show 19 more
-
#38186 fix(gateway): decode schtasks output with locale encoding on Windows (#38172) · merged via another change →
973decc,df848bd,#38334,#38172 -
#38187 fix(cli/windows): decode schtasks output with locale encoding to silence UnicodeDecodeError (#38172) · done elsewhere →
973decc,#38172 -
#39539 fix(windows): tolerate localized taskkill decode errors · other →
0f732cb,#70875,#55339,#60741,#60751 -
#45099 fix(tools): add errors='replace' to subprocess. · merged via another change →
#71014,#70875 -
#45144 fix(windows): set UTF-8 encoding for Copilot ACP subprocess · done elsewhere →
#45099 -
#47951 fix(environments): pass encoding='utf-8' errors='replace' to all text-mode Popen calls · superseded by another PR →
#45099 - #47993 fix(subprocess): add UTF-8 encoding to _popen_bash for Windows (#47939) · other
-
#48838 fix(cli): tolerate undecodable alias lookup output · superseded by another PR →
c89481d,#53428 -
#48839 fix(cli): default Windows subprocess text decoding · duplicate →
#48838 -
#49510 fix(doctor): decode subprocess output as UTF-8 on non-UTF-8 (GBK) locales · done elsewhere →
38b67a3 -
#49512 fix(doctor): pin utf-8/replace decoding for captured subprocesses · superseded by another PR →
#49510,#49499 - #49564 fix(cli): add encoding to subprocess calls in doctor (#49499) · other
-
#53460 fix: add encoding=utf-8, errors=replace to all subprocess text=True calls (#53428) · superseded by another PR →
#54220,#54236,#53892,#54417 -
#53549 fix: add encoding='utf-8' to subprocess calls with text=True · superseded by another PR →
#54220,#54236,#53892,#54417 -
#55339 fix: add explicit UTF-8 encoding to all subprocess text=True calls (#53428) · merged via another change →
#70875,#60751 -
#60832 fix(windows): force utf8 for detached gateway · done elsewhere →
#70850,#70875,#71014 -
#61978 fix: harden tui_gateway subprocess reads against Windows locale UnicodeDecodeError (#53137) · merged via another change →
#70850 -
#63716 fix(tui): decode slash worker streams as UTF-8 · merged via another change →
ead23aa,#70850,#52700,#61978 -
#68546 fix(doctor): decode subprocess output as utf-8 · other →
0f732cb,#70875,#55339,#60741,#60751
Closed issues · 18
- Closed#17049[Bug]: UnicodeDecodeError and AttributeError in Windows gateway process scanning
- Closed#31420[Bug] Windows: subprocess env missing PYTHONUTF8=1 causes UnicodeEncodeError on non-ASCII output in CP936/legacy locales
- Closed#38172Windows: hermes gateway status emits UnicodeDecodeError on localized schtasks output
- Closed#39029Windows: UnicodeDecodeError in subprocess._readerthread — cp1251 can't decode byte 0x98
- Closed#45140[Bug]: UnicodeDecodeError in copilot_acp_client.py _stdout_reader on Windows
- Closed#47939[Bug]: BUG_WINDOWS_UNICODE_SUBPROCESS
- Closed#49491UnicodeDecodeError-on-Windows-doctor
- Closed#49492Bug-Windows-UnicodeDecodeError-doctor
- Closed#49496hermes doctor crashes with UnicodeDecodeError on Chinese Windows GBK locale
- Closed#49499hermes doctor crashes with UnicodeDecodeError on Chinese Windows GBK locale
- Closed#52649Windows: Gateway crashes with UnicodeDecodeError (cp950) in _readerthread — causes zombie cycle every ~580s
- Closed#53137fix: TUI gateway crash on Windows - UnicodeDecodeError in subprocess _readerthread
- Closed#53428subprocess.run(text=True) without encoding param triggers GBK crash on Chinese Windows (21 locations)
- Closed#55020Windows Chinese locale (GBK) causes subprocess._readerthread UnicodeDecodeError crash
- Closed#57238Bug: GBK encoding crash on Chinese Windows[Bug] GBK encoding crash in terminal tool on Chinese Windows (cp936)
- Closed#60760[Windows] Detached gateway crashes with `UnicodeDecodeError: 'gbk'` when a spawned child process emits UTF-8 output
- Closed#61595[Windows] subprocess._readerthread crashes with UnicodeDecodeError on GBK output
- Closed#63686Windows CJK locale: UnicodeDecodeError in _SlashWorker crashes gateway (text=True defaults to GBK)
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.05: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab