fix(shell): preserve UTF-8 native input on PowerShell 5 (#5160)

Windows PowerShell 5.1 defaults $OutputEncoding to US-ASCII, corrupting non-ASCII strings piped to native commands. Set it from the console encoding only on legacy versions so PowerShell 7 keeps its defaults.
This commit is contained in:
Zhou 2026-07-29 21:47:01 +08:00 committed by GitHub
parent 757ad9c764
commit 5a28a6165c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 3 deletions

View File

@ -556,6 +556,7 @@ class ExecTool(Tool):
command = ExecTool._normalize_powershell_command(command) command = ExecTool._normalize_powershell_command(command)
command = ( command = (
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n" "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n"
"if ($PSVersionTable.PSVersion.Major -lt 6) { $OutputEncoding = [Console]::OutputEncoding }\n"
"$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'\n" "$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'\n"
f"{command}\n" f"{command}\n"
"if ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE }" "if ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE }"

View File

@ -230,8 +230,8 @@ class TestSpawnWindows:
assert "if ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE }" in command assert "if ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE }" in command
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_powershell_configures_utf8_output(self): async def test_powershell_configures_utf8_io(self):
"""PowerShell should emit UTF-8 for captured output and redirections.""" """PowerShell should use UTF-8 for captured output, native input, and redirections."""
env = {"PATH": ""} env = {"PATH": ""}
with ( with (
patch("nanobot.agent.tools.shell._IS_WINDOWS", True), patch("nanobot.agent.tools.shell._IS_WINDOWS", True),
@ -242,7 +242,10 @@ class TestSpawnWindows:
command = mock_exec.call_args[0][-1] command = mock_exec.call_args[0][-1]
assert "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)" in command assert "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)" in command
assert "$OutputEncoding =" not in command assert (
"if ($PSVersionTable.PSVersion.Major -lt 6) { "
"$OutputEncoding = [Console]::OutputEncoding }"
) in command
assert "$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'" in command assert "$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'" in command
@pytest.mark.asyncio @pytest.mark.asyncio
@ -821,6 +824,20 @@ class TestWindowsRealExec:
assert b"\x00" not in data assert b"\x00" not in data
assert data.decode("utf-8-sig").strip() == "file café λ 你好" assert data.decode("utf-8-sig").strip() == "file café λ 你好"
@pytest.mark.asyncio
async def test_windows_powershell_native_pipeline_input_is_utf8(self):
python = sys.executable.replace("'", "''")
result = await ExecTool(timeout=180).execute(
command=(
f"[string][char]0x4F1A | & '{python}' "
'-c "import sys; print(sys.stdin.buffer.read().hex())"'
),
shell="powershell",
)
assert "e4bc9a0d0a" in result
assert "Exit code: 0" in result
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_windows_powershell_session_output_is_utf8(self): async def test_windows_powershell_session_output_is_utf8(self):
manager = ExecSessionManager() manager = ExecSessionManager()