From db04d5d570c1fc288c0ccb45cd3f2801870ddc0c Mon Sep 17 00:00:00 2001 From: Rafael Figuereo Date: Tue, 2 Jun 2026 00:20:42 -0400 Subject: [PATCH] fix(cli): force UTF-8 stdout/stderr on Windows to prevent UnicodeEncodeError On Windows, when stdout/stderr are not a UTF-8 TTY (output piped, redirected to a file, or running under a legacy code page such as cp1252), Rich cannot encode the banner and box-drawing glyphs, so the CLI aborts with a UnicodeEncodeError traceback instead of printing. This breaks basic commands like `specify --help` and `specify version` whenever their output is captured rather than written to an interactive terminal. Reconfigure sys.stdout/sys.stderr to UTF-8 with errors="replace" at the main() entry point on win32 so output degrades gracefully instead of crashing. The change is a no-op on POSIX, is guarded by try/except so it can never make stream setup worse, and lives at the CLI entry point only -- importing specify_cli as a library does not touch global streams. Verified on Windows 11 (cp1252): `specify --help` piped and `specify version` redirected to a file both render correctly and exit 0 without setting PYTHONUTF8 / PYTHONIOENCODING. Co-Authored-By: Claude Opus 4.8 --- src/specify_cli/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 4bd23622dd..7c226eedec 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -4780,6 +4780,17 @@ def workflow_catalog_remove( def main(): + # On Windows the default stdout/stderr code page (e.g. cp1252) cannot encode + # the Rich banner and box-drawing glyphs, so the CLI crashes with + # UnicodeEncodeError whenever output is not a UTF-8 TTY (piped, redirected to + # a file, or running under a legacy code page). Force UTF-8 with graceful + # replacement so output degrades instead of aborting. No-op on POSIX. + if sys.platform == "win32": + for _stream in (sys.stdout, sys.stderr): + try: + _stream.reconfigure(encoding="utf-8", errors="replace") + except (AttributeError, ValueError, OSError): + pass app() if __name__ == "__main__":