Skip to content

fix: tolerate subprocess output that cannot be decoded as the locale encoding - #11100

Open
kokokoXUY wants to merge 1 commit into
python-poetry:mainfrom
kokokoXUY:fix/tolerate-undecodable-subprocess-output
Open

kokokoXUY wants to merge 1 commit into
python-poetry:mainfrom
kokokoXUY:fix/tolerate-undecodable-subprocess-output

Conversation

@kokokoXUY

Copy link
Copy Markdown

Pull Request Check List

  • Added tests for changed code.
  • Updated documentation for changed code.

Summary

BaseEnv._run reads the output of the commands it runs with encoding="locale". A command can print bytes that are not valid in that encoding — pip does so on Windows hosts whose code page is not UTF-8 — and reading that output raised UnicodeDecodeError, aborting the caller. On such a host, poetry install fails while installing a distribution.

The read now uses errors="replace", so an undecodable byte becomes U+FFFD instead of an exception. The locale encoding itself is unchanged: hosts whose subprocess output does match the locale decode exactly as before, and non-ASCII output that is valid in the locale is untouched.

Reproduction

On a Windows host with a GBK code page:

$ pytest tests/utils/test_pip.py::test_pip_install_successful
E   UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 20: illegal multibyte sequence
    D:\...\subprocess.py:1196: UnicodeDecodeError

With this change the same test passes.

The added regression test does not depend on that code page: it runs a child process that writes the single byte 0x80 and asserts the call returns instead of raising. Before the change it fails on any platform, because 0x80 is not a valid standalone byte in UTF-8 either.

Changed

  • src/poetry/utils/env/base_env.py: BaseEnv._run decodes subprocess output with errors="replace".
  • tests/utils/env/test_env.py: regression test for undecodable subprocess output.

Validation

before:  tests/utils/env/test_env.py::test_run_tolerates_output_that_cannot_be_decoded
         1 failed (UnicodeDecodeError)
after:   1 passed

before:  tests/utils/test_pip.py::test_pip_install_successful
         1 failed (UnicodeDecodeError: 'gbk' codec ...)
after:   1 passed

after:   pytest tests/utils/env/test_env.py tests/utils/test_pip.py -q
         41 passed, 1 skipped

ruff check reports All checks passed! and both files pass ruff format --check.

Notes

_run is not the only place that reads subprocess output; env_manager.py has two further encoding="locale" reads. They are not touched here — this change is limited to the path I reproduced, and the same treatment can be applied to those if you want it. Untouched as well: the 9 other failures in tests/console/commands/env and tests/utils/test_python_manager.py on this host, which are missing-shell and Windows-specific issues unrelated to decoding.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/utils/env/test_env.py" line_range="613" />
<code_context>
     assert venv_marker_env == system_marker_env
+
+
+def test_run_tolerates_output_that_cannot_be_decoded(tmp_venv: VirtualEnv) -> None:
+    # A command can print bytes that are not valid in the locale encoding: pip
+    # does so on Windows hosts whose code page is not UTF-8 (e.g. GBK). Reading
+    # that output must not raise UnicodeDecodeError.
+    output = tmp_venv._run(
+        [sys.executable, "-c", "import sys; sys.stdout.buffer.write(b'\\x80')"]
+    )
+
+    assert "\ufffd" in output
</code_context>
<issue_to_address>
**issue (testing):** The regression test fails on locales whose encoding accepts byte `0x80` (for example Latin-1 or Windows-1252): the subprocess output decodes to a valid character rather than U+FFFD, so the final assertion fails even though `_run` correctly tolerates the output.

**Triggers:** When the test runs with a non-UTF-8 locale encoding that defines byte `0x80`.

**Suggested fix:** Assert that the call returns successfully and produces output, or choose/assert against a byte sequence known to be undecodable for the active locale rather than requiring U+FFFD.

```suggestion
    assert output
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: tests/utils/env/test_env.py:613


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread tests/utils/env/test_env.py Outdated
…encoding

`BaseEnv._run` reads the output of the commands it runs with
`encoding="locale"`. A command can print bytes that are not valid in that
encoding: on a Windows host whose code page is not UTF-8, pip does so while
installing a distribution, and reading that output raised `UnicodeDecodeError`
and aborted the caller.

Decode with `errors="replace"` so an undecodable byte becomes U+FFFD instead of
an exception. The locale encoding is unchanged, so hosts whose subprocess output
does match the locale keep decoding exactly as before.

Reproduced on a Windows host with a GBK code page:
`tests/utils/test_pip.py::test_pip_install_successful` failed with
`'gbk' codec can't decode byte 0x80 in position 20` and passes with this change.
The regression test writes the same byte from a child process and asserts that
the call returns instead of raising; whether the byte decodes depends on the
active locale (Latin-1 and cp1252 accept it), so the test does not require a
replacement character.
@kokokoXUY
kokokoXUY force-pushed the fix/tolerate-undecodable-subprocess-output branch from 01861d2 to 02c2bb5 Compare September 26, 2026 14:51
@kokokoXUY

Copy link
Copy Markdown
Author

Fixed in 02c2bb5.

You are right that a byte-level assertion was the wrong contract here: whether 0x80 decodes at all depends on the active locale, and Latin-1/cp1252 accept it, so the output can legitimately contain no U+FFFD. The test now asserts that the call returns and produces output — the property the fix actually establishes — and the comment says so explicitly.

Verified locally on a host with a GBK code page: the test passes with the fix in place, and still fails against the unmodified _run, where the read raises before the assertion is reached.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant