Repository navigation
Hosted and lock-only scans treat a UTF-16 requirements.txt (what Windows PowerShell's pip freeze > writes) as absent: exit 0, no warning, and pip keeps installing the unpatched pin #721
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:pippip / requirements.txtpip / requirements.txt
on Oct 3, 2026 - added a commit that references this issue
on Oct 3, 2026 mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Triaged as
priority:p1(pip / requirements.txt). Not a duplicate: #623 is the NuGet UTF-8 BOM reader, and I found no other issue, open or closed, about UTF-16 requirements files. Confirmed onmain(045d7ec):CandidateFiles::readincrates/socket-patch-core/src/hosted/engine.rstreats a diskread_texterror as "absent" (view.read_text(rel).await.ok()), so anInvalidDatadecode is dropped with nolockfile_unreadablewarning, whilevexreports one for the same file.
Generated by Claude Code
mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Claiming this issue (shared root cause: the hosted candidate reader and lock-only discovery treat a requirements file they can't decode as absent). Branch: agent/fix-hosted-unreadable-candidate. Claim-ID: 2026-10-03T21:20:37Z-13ca73
Generated by Claude Code
mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions- added 2 commits that reference this issue
on Oct 3, 2026 mikolalysenko commented
on Oct 4, 2026 CollaboratorAuthorMore actions[agent] New trigger for the same root cause (pip bug-hunt, ledger #309): a requirements.txt with no BOM but a PEP 263 coding line, which pip's
auto_decodehonours.printf '# -*- coding: latin-1 -*-\n# Maintainer: Jos\xe9\nsix==1.16.0\n' > requirements.txt pip install -r requirements.txt # pip 20.3.4 and 26.2.1: rc=0, installs six # (without the coding line pip raises UnicodeDecodeError, so the line is what makes it valid)
On main
045d7ec,scan --mode hosted --jsonexits 0 withredirected: 0and emptyskipped/warnings, both lock-only (scannedPackages: 0) and with a.venvholding six 1.16.0 (scannedPackages: 3). The file is left untouched, so it's the same silent no-op as the UTF-16 case. I ran it twice.Reading the #724 diff,
utils::requirements::decodereturnsNonefor any non-BOM, non-UTF-8 bytes, so this file takes thecandidate_file_unreadablepath rather than being decoded. Refusing it loudly would be acceptable; the remaining risk is a path that still treatsNoneas "absent" (the lock-only inventory doc says "Nonewhen the root file itself cannot be read"). A latin-1 + coding-line fixture next to the UTF-16 ones would pin that down.
Generated by Claude Code
- added 8 commits that reference this issue
on Oct 5, 2026
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
Windows PowerShell 5.1 redirects with UTF-16 LE plus a BOM, so
pip freeze > requirements.txtthere produces a UTF-16 file. pip reads it without complaint:req_file.pyauto-decodes UTF-8, UTF-16 and UTF-32 BOMs, and pip 20.3.4 through 26.2.1 all install from it. socket-patch's hosted scan reads requirements.txt only as UTF-8. On a decode failure it drops the file as though it weren't there, with no diagnostic:status: "success",redirect: {redirected: 0, skipped: [], warnings: []}. Human mode printspkg:pypi/six@1.16.0: no lockfile entry pinning it could be rewritten, which is wrong because the file pins it.No packages found. Run your package manager's install first./scannedPackages: 0, exit 0.The project then installs upstream six with no signal that the file was skipped.
Other commands are loud or correct on the same file.
vexwarnslockfile_unreadable("cannot read requirements.txt: stream did not contain valid UTF-8"), and vendoredvendor/scan --mode vendoredfails withpypi_no_requirements"cannot read …/requirements.txt", exit 1. So only the hosted / discovery path is silent.Impact
A Windows team whose requirements.txt was generated in PowerShell 5.1 (still the default
powershell.exeon Windows 10 and 11) runssocket-patch scan --mode hostedin CI and gets exit 0 with a clean JSON envelope, but nothing is patched. On a fresh checkout it even says there are no packages at all.Repro
This uses the hosted wiremock from
crates/socket-patch-cli/tests/mode_migration_pypi.rs::mount_hosted_api(batch / by-package / package / wheel), with$MOCKas its URI.The same file in UTF-8 (identical text, CRLF) is redirected (
redirected: 1), and a freshpip install -rthen installs the patched six.Expected vs actual
lockfile_unreadableas "A supported file exists but could not be read (permissions, a FIFO squatting the name, non-UTF-8)", andvexalready emits it here. Forsocket.yml, the contract makes UTF-16 an explicit error ("UTF-16 and NUL bytes are errors"). The comment aboveunconfirmedinscan/hosted.rssays an unpinned granted package must never vanish silently. So hosted scan should either decode the BOM the way pip does, or report the file as unreadable inredirect.warnings[]/skipped[](and on stderr). Lock-only discovery should say it couldn't read requirements.txt rather than "No packages found".Matrix
--jsonand human)pip install -ron the UTF-16 filevexon a UTF-16 filelockfile_unreadableis emitted (correct)vendoron a UTF-16 filepypi_no_requirements, exit 1 (loud)macOS and Windows weren't probed. The decode is OS-independent; the file shape is what Windows PowerShell 5.1 writes.
First bad version: none. Published v4.0.0 (PyPI
socket-patch==4.0.0) behaves the same way:Redirected 0 package(s), and lock-only says "No packages found". It isn't a regression.Suspect code
crates/socket-patch-core/src/hosted/engine.rs:324:CandidateFiles::readusesview.read_text(rel).await.ok(), so anInvalidDatadecode error becomes "file absent". The memory-view branch at:334says so explicitly ("a non-UTF-8 one is absent to it as well"). Thenpatch/redirect/requirements.rs::rewritereturns early onfiles.get("requirements.txt")beingNone, with no warning.crates/socket-patch-core/src/vendor/lock_inventory/pypi.rs:711:requirements_treedoesview.read_text(ROOT).await.ok()?, so lock-only discovery drops the root file silently.Probe runs: none (OS-independent text decoding; Linux only).