hermes-agent/tests/tools/test_subprocess_utf8_encoding.py
jinglun010 a23115414a fix: add explicit UTF-8 encoding to _op_whoami subprocess call (#53428)
PR #55339 adds encoding='utf-8', errors='replace' to 26 subprocess.run(text=True)
call sites across the codebase. The triage review (thanks @alt-glitch) diffed
this PR against #55339 and found that 5 of the 6 originally-touched call sites
are already covered there byte-identically:

- hermes_cli/main.py::_probe_container
- hermes_cli/setup.py SSH probe
- tools/tts_tool.py::_generate_neutts
- tools/transcription_tools.py::_prepare_local_audio
- tools/transcription_tools.py::_transcribe_local_command (both branches)

The one genuinely net-new site — hermes_cli/onepassword_secrets_cli.py::_op_whoami
(the 1Password op CLI whoami probe) — is NOT in #55339 and is fixed here.

Without explicit encoding=, text=True decodes child output with
locale.getpreferredencoding(False) — cp936 on Chinese Windows — which crashes
_readerthread on non-GBK bytes, cascading into pipe buffer fills, event loop
stalls, and TUI freezes (issues #47939, #53428, #57238).

Scope narrowed per triage feedback: the other 5 sites should land via #55339.

Refs #53428 (together with #55339).
2026-07-24 11:45:57 -07:00

52 lines
2.2 KiB
Python

"""Regression test for issue #53428 — subprocess.run(text=True) without
explicit ``encoding=`` triggers ``UnicodeDecodeError`` on Chinese Windows
(cp936/GBK default encoding).
PR #55339 covers 21 call sites in ``agent/``, ``gateway/``, ``cli.py``,
``cron/``, plus 5 more in ``tools/`` and ``hermes_cli/`` (main.py,
setup.py, tts_tool.py, transcription_tools.py). The one call site it
misses — ``hermes_cli/onepassword_secrets_cli.py::_op_whoami`` — is
guarded here.
Without ``encoding=``, ``text=True`` decodes child output with
``locale.getpreferredencoding(False)`` — cp936 on Chinese Windows —
which crashes on non-GBK bytes (issues #47939, #53428, #57238).
"""
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch, MagicMock
def _assert_utf8_kwargs(mock_run):
"""Lift the encoding/errors kwargs out of the mock and assert them."""
assert mock_run.called, "subprocess.run was not called"
kwargs = mock_run.call_args.kwargs
assert kwargs.get("encoding") == "utf-8", (
f"subprocess.run called without encoding='utf-8' "
f"(got encoding={kwargs.get('encoding')!r}). "
f"On Chinese Windows (cp936), text=True without explicit encoding "
f"crashes with UnicodeDecodeError on non-GBK bytes. See #53428."
)
assert kwargs.get("errors") == "replace", (
f"subprocess.run called without errors='replace' "
f"(got errors={kwargs.get('errors')!r}). encoding='utf-8' alone "
f"still raises UnicodeDecodeError on non-UTF-8 bytes emitted by "
f"Windows-native CLIs. See #53428."
)
def test_op_whoami_passes_utf8_encoding(tmp_path):
"""_op_whoami must pass encoding='utf-8', errors='replace' so op CLI
output containing non-ASCII account names doesn't crash on cp936."""
from hermes_cli import onepassword_secrets_cli as op_cli
fake_binary = tmp_path / "op"
fake_binary.write_bytes(b"")
with patch.object(op_cli.subprocess, "run") as mock_run:
mock_run.return_value = MagicMock(
returncode=0, stdout="user@example.com", stderr=""
)
op_cli._op_whoami(fake_binary, account="")
_assert_utf8_kwargs(mock_run)