fix(windows): make redirected output UTF-8, independent of the codepage - #567
Merged
Conversation
padak
added a commit
that referenced
this pull request
Aug 11, 2026
Same rule that was raised on #567: CONTRIBUTING requires a (since vX.Y.Z) entry in the agent-facing behaviour log for anything an agent would not infer from --help. Three of these qualify and none had one. Two commands failed on EVERY Windows invocation before 0.80.1 and worked everywhere else, so an agent seeing the report needs to know it is the platform, not the project -- including what to do on an older kbagent, which for --idempotency-key means not trusting it at all, since it never recorded an entry there. The doctor change is the one an agent can actually mis-handle: config_file went from a permanent, unfixable warn to pass on Windows, so anything treating warn as actionable was reporting a problem with no remedy.
`kbagent semantic-layer --help` and `kbagent context` exit 1 with
`UnicodeEncodeError` on Windows the moment their output is piped or redirected,
and any Rich table truncated by width emits a lone `0x85` for its ellipsis.
The split that matters is terminal vs not, not which codepage is active. Since
PEP 528 CPython writes to a real Windows console through the console API, so an
interactive kbagent already reports `encoding=utf-8` and renders anything --
measured on Windows 11:
stdout encoding "arrow"
console utf-8 encodes
pipe / file cp1252 UnicodeEncodeError
Redirect it and that path is gone; Python falls back to the locale encoding,
which cannot represent an arrow, an em dash, or a box-drawing glyph. Scripts,
CI and AI agents capturing output are the ones affected -- this CLI's primary
audience -- while the interactive users who would notice never see it.
So: reconfigure stdout/stderr to UTF-8 only when the stream is NOT a terminal.
Terminals are deliberately left alone, because forcing UTF-8 bytes at a cp852
console would replace a working display with mojibake. Redirected Windows
output now matches POSIX byte for byte, including the box-drawing characters
Rich previously downgraded to ASCII.
human/Rich path cannot use that escape hatch because Rich owns the writes.
Verified on a real Windows 11 box, piped:
before: semantic-layer --help -> CRASH context -> CRASH (invalid UTF-8)
after: semantic-layer --help -> clean context -> clean (valid UTF-8)
and under a real console, all three surfaces stay rc=0 and clean, confirming
the interactive path is untouched.
Ships in 0.80.1 alongside #566, rather than as its own version.
CONTRIBUTING requires a (since vX.Y.Z) entry in the agent-facing behaviour log for anything an agent would not infer from --help, and this is squarely that: before v0.80.1 an agent piping `semantic-layer --help` or `context` on Windows got exit 1, while a human typing the same command saw it work. Raised in review. Placed directly after the #546 entry, whose "human (Rich) output is unaffected" line was about to become half-true, and linked from it. Documents the older- version workaround verified against the installed build rather than copied from the issue: PYTHONUTF8=1 and PYTHONIOENCODING=utf-8 each turn the crash into clean output on their own.
padak
force-pushed
the
fix/windows-redirected-output-utf8
branch
from
August 11, 2026 15:39
fac2262 to
b411065
Compare
Member
Author
|
Rebased onto main after #566 landed, and renumbered to ship inside 0.80.1 rather than as its own 0.80.2 — one release for the whole Windows batch. Three things moved as part of that, none of them mechanical:
Version files, all six local gates and 5411 tests are green. |
This was referenced Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
kbagent semantic-layer --helpandkbagent contextexit 1 withUnicodeEncodeErroron Windows the moment their output is piped or redirected, and any Rich table truncated by width emits a lone0x85where its ellipsis should be.The split that matters is terminal vs not — not which codepage is active
I assumed at first this was a cp1250/cp852 problem and that the fix would have to trade a crash for mojibake on the console. Measuring it says otherwise. Since PEP 528, CPython writes to a real Windows console through the console API, so an interactive
kbagentalready reportsutf-8and renders anything:encoding"↔"isatty=True)isatty=False)UnicodeEncodeErrorRedirect it and that path is gone — Python falls back to the locale encoding, which cannot represent an arrow, an em dash, or a box-drawing glyph. Running the same command under four different
chcpvalues produces byte-identical output, because the console codepage has no say once stdout is a pipe.So the people hit by this are scripts, CI, and AI agents capturing output — this CLI's stated primary audience — while the interactive users who would notice a change never see it.
The fix
Reconfigure
stdout/stderrto UTF-8 only when the stream is not a terminal.Terminals are deliberately left untouched. Forcing UTF-8 bytes at a console whose codepage is cp852 would replace a working display with mojibake, which is why the naive "just force UTF-8 everywhere" version of this fix is wrong. Redirected Windows output now matches POSIX byte for byte, including the box-drawing characters Rich had been downgrading to ASCII.
#546 fixed this same class for
--jsonby writing bytes straight tosys.stdout.buffer. The human/Rich path cannot use that escape hatch, because Rich owns the writes — hence the stream-level fix.The call sits at import rather than in the root callback: Click renders
--helpwhile parsing, before any callback runs, and--helpis one of the crashing surfaces.Verified on a real Windows 11 machine
Piped, before and after, same commands:
semantic-layer --helpcontextPreviously-mojibake surfaces (
project list,permissions list,--help,--json changelog) all now emit valid UTF-8.And the no-regression half — under a real console, with the fix in place:
Tests
Seven tests covering the contract on every platform: a redirected non-UTF-8 stream is switched; a terminal is left alone; an already-UTF-8 stream (POSIX, and a real Windows console) is untouched across three spellings; the error handler is preserved (
surrogateescapeis what round-trips undecodable filename bytes); and streams that lackreconfigureor refuse it do not take the CLI down.Local suite: 5410 passed, 0 failed.
Notes