From d211537fdcf19a8f404358ac93d3dc1e02915fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E8=B6=8A=E7=BE=BD=E6=AF=9B?= <97326386+Icather@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:25:00 +0800 Subject: [PATCH] fix(tools): add errors='replace' to subprocess text-mode pipes on non-UTF-8 Windows - agent/copilot_acp_client.py - tools/environments/base.py - tools/transcription_tools.py - tools/tts_tool.py - scripts/run_tests_parallel.py - tui_gateway/server.py (5 sites, covers slash-worker + pdftoppm + cli.exec + quick-cmd + shell-cmd) Dropped code_execution_tool.py (now byte-mode with post-decode) and photon/adapter.py (main already safe) per sweeper review. --- agent/copilot_acp_client.py | 2 ++ scripts/run_tests_parallel.py | 2 ++ tools/environments/base.py | 2 ++ tools/transcription_tools.py | 2 ++ tools/tts_tool.py | 2 ++ tui_gateway/server.py | 10 ++++++++-- 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/agent/copilot_acp_client.py b/agent/copilot_acp_client.py index 5e095af3902b..28cb20155605 100644 --- a/agent/copilot_acp_client.py +++ b/agent/copilot_acp_client.py @@ -509,6 +509,8 @@ def _run_prompt(self, prompt_text: str, *, timeout_seconds: float) -> tuple[str, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", bufsize=1, cwd=self._acp_cwd, env=_build_subprocess_env(), diff --git a/scripts/run_tests_parallel.py b/scripts/run_tests_parallel.py index 602e033f3d6d..dae22f33b7f4 100755 --- a/scripts/run_tests_parallel.py +++ b/scripts/run_tests_parallel.py @@ -260,6 +260,8 @@ def _run_one_file( stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, + encoding="utf-8", + errors="replace", env=os.environ, # POSIX: place the child at the head of its own process group so # _kill_tree can SIGKILL the group atomically. diff --git a/tools/environments/base.py b/tools/environments/base.py index 846003432c0a..1c37aff8d3ec 100644 --- a/tools/environments/base.py +++ b/tools/environments/base.py @@ -149,6 +149,8 @@ def _popen_bash( stderr=subprocess.STDOUT, stdin=subprocess.PIPE if stdin_data is not None else subprocess.DEVNULL, text=True, + encoding="utf-8", + errors="replace", **kwargs, ) if stdin_data is not None: diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index 39e92261a19c..8c038c44fb81 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -554,6 +554,8 @@ def _run_command_stt(command: str, timeout: float) -> subprocess.CompletedProces "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "text": True, + "encoding": "utf-8", + "errors": "replace", } if os.name == "nt": popen_kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0) diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 545d72bb6907..8aafb8e4e107 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -780,6 +780,8 @@ def _run_command_tts(command: str, timeout: float) -> subprocess.CompletedProces "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "text": True, + "encoding": "utf-8", + "errors": "replace", } if os.name == "nt": popen_kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0) diff --git a/tui_gateway/server.py b/tui_gateway/server.py index af5cead103c1..e4b21f6861e6 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -317,6 +317,8 @@ def __init__(self, session_key: str, model: str, profile_home: str | None = None stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", bufsize=1, cwd=os.getcwd(), env=env, @@ -9767,7 +9769,7 @@ def _(rid, params: dict) -> dict: try: res = subprocess.run( - argv, capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL, + argv, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags(), ) except subprocess.TimeoutExpired: @@ -11864,6 +11866,8 @@ def _(rid, params: dict) -> dict: [sys.executable, "-m", "hermes_cli.main", *argv], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=min(int(params.get("timeout", 240)), 600), cwd=os.getcwd(), # cli.exec runs `python -m hermes_cli.main` (can drive the agent) → @@ -11934,6 +11938,8 @@ def _(rid, params: dict) -> dict: shell=True, capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=30, stdin=subprocess.DEVNULL, env=sanitized_env, @@ -14481,7 +14487,7 @@ def _(rid, params: dict) -> dict: return _err(rid, 5001, "shell.exec unavailable: approval safety module not importable") try: r = subprocess.run( - cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd(), + cmd, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=30, cwd=os.getcwd(), stdin=subprocess.DEVNULL, ) return _ok(