fix: force UTF-8 stdout on Windows for CJK CLI output

On Windows the default Python stdout encoding follows the system
ANSI code page (cp950 for zh-TW, cp932 for ja, cp949 for ko).
`click.echo()` then raises `UnicodeEncodeError` whenever a CJK
character lands in CLI output — `jarvis ask` returning Chinese
crashes with `'cp950' codec can't encode character '义'`.

Reconfigure `sys.stdout` and `sys.stderr` to UTF-8 with
`errors='replace'` at the `main()` entry point. Scoped to
`win32` so other platforms are untouched. Two unit tests verify
the reconfigure happens on Windows and doesn't on Linux.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
IsaacH
2026-05-20 03:36:55 +00:00
committed by krypticmouse
co-authored by Claude Opus 4.7
parent 31efd9e672
commit 30ac635e83
2 changed files with 49 additions and 1 deletions
+9
View File
@@ -140,6 +140,15 @@ except ImportError:
def main() -> None:
"""Entry point registered as ``jarvis`` console script."""
import sys
if sys.platform == "win32":
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, OSError):
pass
cli()
+40 -1
View File
@@ -2,13 +2,52 @@
from __future__ import annotations
import io
import sys
from pathlib import Path
from unittest import mock
from click.testing import CliRunner
import openjarvis
from openjarvis.cli import cli
from openjarvis.cli import cli, main
class TestMainEntryPoint:
"""Tests for the ``jarvis`` console script entry point."""
def test_windows_reconfigures_stdout_to_utf8(self) -> None:
"""On Windows, main() must reconfigure stdout/stderr to UTF-8 so that
CJK characters in CLI output don't trigger UnicodeEncodeError under
legacy code pages (cp950, cp932, cp949)."""
stdout_mock = mock.MagicMock(spec=io.TextIOWrapper)
stderr_mock = mock.MagicMock(spec=io.TextIOWrapper)
with (
mock.patch.object(sys, "platform", "win32"),
mock.patch.object(sys, "stdout", stdout_mock),
mock.patch.object(sys, "stderr", stderr_mock),
mock.patch("openjarvis.cli.cli") as cli_mock,
):
main()
stdout_mock.reconfigure.assert_called_once_with(
encoding="utf-8", errors="replace"
)
stderr_mock.reconfigure.assert_called_once_with(
encoding="utf-8", errors="replace"
)
cli_mock.assert_called_once()
def test_non_windows_does_not_reconfigure(self) -> None:
"""On non-Windows platforms, stdout/stderr are left untouched."""
stdout_mock = mock.MagicMock(spec=io.TextIOWrapper)
with (
mock.patch.object(sys, "platform", "linux"),
mock.patch.object(sys, "stdout", stdout_mock),
mock.patch("openjarvis.cli.cli") as cli_mock,
):
main()
stdout_mock.reconfigure.assert_not_called()
cli_mock.assert_called_once()
class TestCLI: