Problem
#1574 pins encoding="utf-8" on every text-mode subprocess call in this repository, so a parent decodes its child as UTF-8 on every host. Where that child is another Python interpreter, its own encoder is still the locale's, and the two sides now disagree on Windows in a way they did not before.
Before that change both sides took the locale codec, so they matched, whatever the locale was. After it the parent names UTF-8 while a redirected Python child on Windows writes its stdout in the ANSI code page, so any non-ASCII character the child prints comes back as a byte sequence the parent cannot decode.
Around fourteen call sites spawn sys.executable this way, in host-setup/agent-safety/claude/test_install.py, scripts/tests/test_build_dist.py, scripts/tests/test_prose_lint.py and scripts/tests/test_spec_validate.py.
Evidence
Constructed, using an environment variable to stand in for a Windows ANSI host, since the fleet has no Windows runner:
subprocess.run([sys.executable, <a script that prints a path>],
text=True, encoding="utf-8",
env={..., "PYTHONIOENCODING": "cp1252"})
-> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9
The child prints a path holding a non-ASCII character, encodes it as cp1252, and the parent decodes as UTF-8.
Reach
A test run on a Windows host, which is the platform GOVERNANCE.md "Supported Development Platforms" declares supported and which nothing in CI exercises. A Linux run is unaffected, since both sides resolve to UTF-8 anyway.
One site in the tree already guards this, test_a_filename_that_is_not_utf8_is_scanned_and_reported in scripts/tests/test_prose_lint.py, by passing PYTHONIOENCODING=utf-8 to the child. So the mechanism is known and applied in one place rather than as a rule.
Suggested fix
Decide the rule, then apply it and assert it. A parent that pins its decoder and spawns a Python child owes that child the matching encoder, PYTHONIOENCODING=utf-8 being the one that reaches every output stream. scripts/tests/test_tooling_encoding.py asserts the parent half of this invariant already and is where the child half belongs.
Sibling of #1538. Found by a local review pass on #1574.
Problem
#1574 pins
encoding="utf-8"on every text-modesubprocesscall in this repository, so a parent decodes its child as UTF-8 on every host. Where that child is another Python interpreter, its own encoder is still the locale's, and the two sides now disagree on Windows in a way they did not before.Before that change both sides took the locale codec, so they matched, whatever the locale was. After it the parent names UTF-8 while a redirected Python child on Windows writes its stdout in the ANSI code page, so any non-ASCII character the child prints comes back as a byte sequence the parent cannot decode.
Around fourteen call sites spawn
sys.executablethis way, inhost-setup/agent-safety/claude/test_install.py,scripts/tests/test_build_dist.py,scripts/tests/test_prose_lint.pyandscripts/tests/test_spec_validate.py.Evidence
Constructed, using an environment variable to stand in for a Windows ANSI host, since the fleet has no Windows runner:
The child prints a path holding a non-ASCII character, encodes it as cp1252, and the parent decodes as UTF-8.
Reach
A test run on a Windows host, which is the platform
GOVERNANCE.md"Supported Development Platforms" declares supported and which nothing in CI exercises. A Linux run is unaffected, since both sides resolve to UTF-8 anyway.One site in the tree already guards this,
test_a_filename_that_is_not_utf8_is_scanned_and_reportedinscripts/tests/test_prose_lint.py, by passingPYTHONIOENCODING=utf-8to the child. So the mechanism is known and applied in one place rather than as a rule.Suggested fix
Decide the rule, then apply it and assert it. A parent that pins its decoder and spawns a Python child owes that child the matching encoder,
PYTHONIOENCODING=utf-8being the one that reaches every output stream.scripts/tests/test_tooling_encoding.pyasserts the parent half of this invariant already and is where the child half belongs.Sibling of #1538. Found by a local review pass on #1574.