Skip to content

A Parent Pinned to UTF-8 Spawning a Python Child Leaves the Child's Encoder on the Locale #1577

Description

@ptr727

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions