[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
On a checkout with no virtualenv yet (the usual CI / fresh-clone case), scan only finds the pins written directly in the root requirements.txt. A pin reached through an -r include (-r requirements/base.txt, the common split-requirements layout) never joins discovery. Both scan (hosted) and scan --mode vendored print No patches available for installed packages. and exit 0 / success, with lockfileOnlyPackages: 0 and no warning. pip install -r requirements.txt then installs the unpatched upstream package.
With an installed .venv, the same project is vendored: the vendored writer rewrites the pin inside requirements/base.txt, and pip install -r requirements.txt gets the patched wheel. Only the lockfile-only inventory is missing the include.
Impact
Patches are silently skipped for every dependency pinned in an included file on lockfile-only runs. The scan reports success, so nothing tells the user the pins were never looked at.
Repro
Uses a local mock of the patch API that serves a patched six-1.16.0 wheel, the same shape as tests/vex_pypi_real_common::RealApi. Linux, CPython 3.11, pip 24.0, main 2463257.
A="--api-url http://127.0.0.1:8765 --api-token fake --org test-org --patch-server-url http://127.0.0.1:8765"
mkdir -p p/requirements && cd p
printf -- '-r requirements/base.txt\n' > requirements.txt
printf 'six==1.16.0\n' > requirements/base.txt
socket-patch scan --mode vendored --json $A | jq '{status, lockfileOnlyPackages, packages: [.packages[].purl]}'
# {"status":"success","lockfileOnlyPackages":0,"packages":[]}
socket-patch scan --json $A | jq '{status, lockfileOnlyPackages}' # hosted: same
python -m venv v && v/bin/pip install -r requirements.txt && v/bin/python -c 'import six; print(getattr(six,"SOCKET_PATCHED",0))' # 0 → unpatched
Control: the same pin written directly in requirements.txt gives lockfileOnlyPackages: 1 and packages: ["pkg:pypi/[email protected]"], and is rewritten in both modes.
| layout |
mode |
lockfileOnlyPackages |
rewritten |
pip install -r |
root requirements.txt |
hosted |
1 |
yes |
patched |
root requirements.txt |
vendored |
1 |
yes |
patched |
-r requirements/base.txt |
hosted |
0 |
no |
unpatched |
-r requirements/base.txt |
vendored |
0 |
no |
unpatched |
-r requirements/base.txt + populated .venv |
vendored |
n/a (installed) |
yes, in base.txt |
patched |
All Linux rows reproduced twice.
Expected vs actual
- Expected: CLI_CONTRACT.md lists vendored pypi wiring as "
requirements.txt + its in-root -r includes", and the hosted unwind coverage lists "requirements.txt (+ in-root -r includes)". The "Lockfile supplement (v3.4)" paragraph says pinned requirements.txt dependencies with no installed copy join discovery. The files the vendored writer edits should be the files the lockfile-only inventory reads.
- Actual: only the root file is inventoried, so included pins are invisible until something is installed.
For hosted mode, rewriting only the root file is documented: an installed pin found only in an include gets redirect_requirements_entry_not_found. On a lockfile-only checkout, though, even that warning is missing, because the package never enters discovery.
OS × version
| OS |
pip / Python |
include layout, hosted |
include layout, vendored |
root-file control |
| Linux (sandbox) |
20.3.4/3.10, 23.3.2/3.11, 24.3.1/3.12, 26.2.1/3.13 (install of the untouched include project) |
not discovered, unpatched |
not discovered, unpatched |
patched |
| ubuntu-latest (probe) |
20.3.4 / 3.8 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| ubuntu-latest (probe) |
25.0.1 / 3.8 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| ubuntu-latest (probe) |
26.2.1 / 3.13 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| macos-latest (probe) |
20.3.4 / 3.8 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| macos-latest (probe) |
25.0.1 / 3.8 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| macos-latest (probe) |
26.2.1 / 3.13 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| windows-latest (probe) |
20.3.4 / 3.8 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| windows-latest (probe) |
25.0.1 / 3.8 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| windows-latest (probe) |
26.2.1 / 3.13 |
not discovered, unpatched |
not discovered, unpatched |
patched |
| all 3 OS (probe) |
20.3.4 / 3.13 |
blocked: pip 20.3.4 can't run on 3.13 |
|
|
First bad
Not a regression. v4.0.0 behaves the same: scan --mode vendored on the include layout vendors nothing.
Suspect code
crates/socket-patch-core/src/vendor/lock_inventory/pypi.rs:587-588: inventory_requirements_txt reads only view.read_text("requirements.txt") and skips every --prefixed line (-r / -c included) without following it. The vendored writer's include walk (vendor/pypi_requirements.rs:599-724, requirements_include_names) already resolves in-root includes and could be shared.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36806309982
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
On a checkout with no virtualenv yet (the usual CI / fresh-clone case),
scanonly finds the pins written directly in the rootrequirements.txt. A pin reached through an-rinclude (-r requirements/base.txt, the common split-requirements layout) never joins discovery. Bothscan(hosted) andscan --mode vendoredprintNo patches available for installed packages.and exit 0 /success, withlockfileOnlyPackages: 0and no warning.pip install -r requirements.txtthen installs the unpatched upstream package.With an installed
.venv, the same project is vendored: the vendored writer rewrites the pin insiderequirements/base.txt, andpip install -r requirements.txtgets the patched wheel. Only the lockfile-only inventory is missing the include.Impact
Patches are silently skipped for every dependency pinned in an included file on lockfile-only runs. The scan reports success, so nothing tells the user the pins were never looked at.
Repro
Uses a local mock of the patch API that serves a patched
six-1.16.0wheel, the same shape astests/vex_pypi_real_common::RealApi. Linux, CPython 3.11, pip 24.0, main2463257.Control: the same pin written directly in
requirements.txtgiveslockfileOnlyPackages: 1andpackages: ["pkg:pypi/[email protected]"], and is rewritten in both modes.pip install -rrequirements.txtrequirements.txt-r requirements/base.txt-r requirements/base.txt-r requirements/base.txt+ populated.venvAll Linux rows reproduced twice.
Expected vs actual
requirements.txt+ its in-root-rincludes", and the hosted unwind coverage lists "requirements.txt(+ in-root-rincludes)". The "Lockfile supplement (v3.4)" paragraph says pinnedrequirements.txtdependencies with no installed copy join discovery. The files the vendored writer edits should be the files the lockfile-only inventory reads.For hosted mode, rewriting only the root file is documented: an installed pin found only in an include gets
redirect_requirements_entry_not_found. On a lockfile-only checkout, though, even that warning is missing, because the package never enters discovery.OS × version
First bad
Not a regression. v4.0.0 behaves the same:
scan --mode vendoredon the include layout vendors nothing.Suspect code
crates/socket-patch-core/src/vendor/lock_inventory/pypi.rs:587-588:inventory_requirements_txtreads onlyview.read_text("requirements.txt")and skips every--prefixed line (-r/-cincluded) without following it. The vendored writer's include walk (vendor/pypi_requirements.rs:599-724,requirements_include_names) already resolves in-root includes and could be shared.Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36806309982