devops_utils.core.encoding module

Make CLI output survive non-UTF-8 stdio (Windows cp1252 in particular).

Work-item titles, previews, and this CLI’s own messages contain non-ASCII characters (→, —). On Windows a redirected stdout defaults to the ANSI code page (cp1252), where writing one of those raises UnicodeEncodeError and kills the command mid-write:

UnicodeEncodeError: 'charmap' codec can't encode character '\u2190'

configure_stdio() upgrades such streams to UTF-8 and, whatever the encoding ends up being, switches the error handler to backslashreplace so an unencodable character degrades to an escape sequence instead of an exception.

It is called from every entry point — the devops-utils console script (from its main wrapper, before Click parses argv, so eager options like --help are covered too), the root Click group (covering python -m and embedded use), and the devops-utils-mcp server via configure_stderr(). The point is that none of this depends on the caller exporting PYTHONUTF8 / PYTHONIOENCODING first.

stdout is the stream that matters: CPython already defaults sys.stderr to backslashreplace, so Click’s own error output was never the crash path.

Note that chcp 65001 was never part of the fix: since PEP 528 a process attached to a real Windows console writes through WriteConsoleW whatever the code page is. The crash only ever happened on a redirected stream, which falls back to the ANSI code page.

devops_utils.core.encoding.ERRORS = 'backslashreplace'

never raise, escape what can’t be encoded (←) so the text stays readable and diagnosable.

Type:

Error handler used for all CLI output

devops_utils.core.encoding.configure_stderr() → None[source]

Harden sys.stderr only — for hosts that own stdout.

The MCP stdio transport frames JSON-RPC over stdout and stdin; those must stay byte-exact UTF-8, and backslashreplace at the stream layer would silently mangle a frame rather than let the serializer escape it. Its log channel is stderr, though, which is exposed to the same cp1252 crash as the CLI’s.

devops_utils.core.encoding.configure_stdio() → None[source]

Apply UTF-8 + backslashreplace to sys.stdout and sys.stderr.

Idempotent, so it is safe to call from more than one entry point.

stdin is deliberately left alone: the only readers are click.confirm and devops_utils.core.io.input_confirm(), both of which take y/n. A future “read the description from stdin” feature should reconfigure at its own call site rather than globally.