Repository navigation
Lock-only scans still drop a requirements.txt pip decodes through a PEP 263 coding line (latin-1): "No pypi packages found", exit 0, while the same project with a venv refuses candidate_file_unreadable #1119
Description
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
#724 (fix for #721) made lock-only discovery decode a requirements file by its BOM, the way pip does, and made hosted mode refuse a candidate file it can't decode (candidate_file_unreadable). pip's auto_decode has one more rule: with no BOM it honours a PEP 263 # -*- coding: <enc> -*- line in the first two lines. utils::requirements::decode returns None for such a file, and requirements_tree in lock-only discovery reads None as "no requirements file". So on a fresh checkout (no venv), scan prints No pypi packages found. and exits 0 with nothing pinned, and pip goes on to install the unpatched pin.
The same file in a project whose venv holds the package is refused loudly (candidate_file_unreadable, exit 1), so the refusal only fires when something else discovered the package first. A -r include in that encoding is dropped silently as well.
I raised this case in a comment on #721 before #724 merged (#721 (comment)). #721 was then closed with only the BOM cases fixed, so I'm filing the remainder here.
Impact
A CI job that runs socket-patch scan --mode hosted (or --mode vendored) on a fresh checkout before pip install -r requirements.txt reports success with nothing found. The install then pulls the unpatched release, and nothing in the output or the --json envelope says that a requirements file was skipped. The likeliest real-world trigger is a requirements file with a non-ASCII comment (a maintainer name, say) in a legacy encoding. pip accepts it whenever it carries a coding line.
Repro (Linux, main b96a785)
# Run in an empty directory, with VIRTUAL_ENV pointing at an EMPTY venv (fresh checkout / lock-only).
printf '# -*- coding: latin-1 -*-\n# Maintainer: Jos\xe9\nsix==1.16.0\n' > requirements.txt
pip install --no-deps --target t -r requirements.txt # pip 20.3.4 / 24.0 / 26.2.1: rc=0, installs six 1.16.0
# (without the coding line, pip fails: UnicodeDecodeError, so the coding line is what makes the file valid)
socket-patch scan --mode hosted --yes --ecosystems pypi
# -> "No pypi packages found." exit 0, requirements.txt unchanged
socket-patch scan --mode hosted --yes --json --ecosystems pypi
# -> {"status":"success","scannedPackages":0, ..., "redirect":{"redirected":0,"patches":[],"skipped":[],"warnings":[]}}
socket-patch scan --mode vendored --yes --json --ecosystems pypi
# -> status success, scannedPackages 0, exit 0
# Same file, but with a .venv holding six==1.16.0:
socket-patch scan --mode hosted --yes --json --ecosystems pypi
# -> exit 1, "requirements.txt is not UTF-8 text ... re-save it as UTF-8 and re-run; nothing was written"
# Include variant (lock-only): requirements.txt = "-r dev.txt", dev.txt = the latin-1 file above
# -> "No pypi packages found." exit 0; pip installs six from dev.txt.socket-patch vex on the same lock-only checkout does warn cannot read requirements.txt: stream did not contain valid UTF-8, so the scan is the only silent path.
The patch API was a local mock serving a patch for pkg:pypi/six@1.16.0. Scans with a venv find and offer it.
Expected vs actual
- Expected: lock-only discovery decodes the file the way pip does, BOM first and then the PEP 263 coding line (the Fix UTF-16 requirements.txt silently skipped (#721) #724 commit message says it "decodes requirements.txt and its -r includes by BOM the way pip does"). Failing that, it should refuse or warn by name, like the venv path's
candidate_file_unreadableandvex'slockfile_unreadable. A silent success contradicts the reason Hosted and lock-only scans treat a UTF-16 requirements.txt (what Windows PowerShell'spip freeze >writes) as absent: exit 0, no warning, and pip keeps installing the unpatched pin #721 was fixed: a requirements file pip installs from must not be treated as absent. - Actual: exit 0,
No pypi packages found., an emptyredirectblock and no warning, for both the root file and a-rinclude.
OS × version
| Cell | Lock-only hosted / vendored scan | With venv (hosted) | pip installs the file |
|---|---|---|---|
| Linux, pip 20.3.4 / py3.8 | silent, exit 0 | — | yes |
| Linux, pip 24.0 / py3.12 | silent, exit 0 | — | yes |
| Linux, pip 26.2.1 / py3.11 | silent, exit 0 (×2, root and include) | refuses candidate_file_unreadable, exit 1 |
yes |
| uv pip (control) | — | — | no ("failed to decode file"), so pip only |
| macOS / Windows | untested; the decode is OS-independent. On Windows, pip's no-BOM fallback is the locale codepage, so an ANSI (cp1252) file with no coding line is also valid there |
First bad: this never worked. Before #724 every non-UTF-8 file was skipped (#721); #724 fixed only the BOM encodings.
Suspect code
crates/socket-patch-core/src/utils/requirements.rs:26(decode): BOMs, thenString::from_utf8, with no PEP 263 coding-line step (pip:pip/_internal/utils/encoding.py::auto_decode).crates/socket-patch-core/src/vendor/lock_inventory/pypi.rs:733(requirements_tree):read(ROOT)?turns an undecodable root intoNone(no requirements), and an undecodable include iscontinued, with no diagnostic in either case.
No probe runs: the sandbox can't delete probe branches, and the defect is in OS-independent decoding.
Backlog review — 2026-10-08
Priority: P1 → P2. Lock-only discovery misses a PEP 263 non-UTF8 requirements file. Retain support for the uncommon encoding; no false VEX evidence is claimed.
Activity
pip: run 31 ledger (#1119, #638 comment)
mikolalysenko commentedon Oct 8, 2026
[agent] Triage: priority:p1 (pip / PyPI family). Confirmed on main
b96a785:utils/requirements.rsdecodehas no PEP 263 coding-line step, andvendor/lock_inventory/pypi.rsrequirements_treereturnsNonefor an undecodable root andcontinues past an undecodable include with no diagnostic. Not a duplicate of #721 (closed): that fix covered BOM encodings only.Shares root cause with #1120: requirements-file readers have no single pip-compatible decode-or-refuse entry point, so each call site either reads strict UTF-8 or maps an undecodable file to "absent". Will be fixed together.
Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Claiming this issue (with #1120; shared root cause: requirements-file readers have no single pip-compatible decode-or-refuse entry point). Branch: agent/fix-requirements-decode. Claim-ID: 2026-10-08T17:21:08Z-c0ee20
Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Draft PR: #1152.
Generated by Claude Code
Read requirements files the way pip decodes them
Use the shared BOM helper in the requirements decoder
pip bug-hunt run 2026-10-09: file #1212, verify #1119