[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
The vendored requirements.txt flavor decides whether a vendor line is still "ours" in two different ways:
So any edit to the trailing # socket-patch vendor: six==1.16.0 comment leaves a project that installs the patched wheel fine but can never be reverted. The edit can be a changed space, a user's own note appended after it, or the comment stripped by a formatter. vendor --revert prints vendor_revert_line_drifted, keeps the wheel and the ledger entry, and exits 0 with status: "success". Its remedy text says to "undo the drift (restore the vendored lock entries or re-vendor) and re-run vendor --revert". But:
- When the comment was reformatted or extended,
vendor reports already_vendored ("artifact and lockfile wiring already in sync") and changes nothing. Re-running vendor --revert drift-keeps again, forever.
- When the comment was stripped,
vendor doesn't recognise the bare wheel path as six's pin. It appends a second wiring line for the same wheel, ./…/six-1.16.0-py2.py3-none-any.whl # socket-patch vendor: six==1.16.0 (transitive). A later vendor --revert removes only that appended line. The user's bare path line stays, the original six==1.16.0 pin is never restored, and the run exits 0 again.
Nothing tells the user which exact bytes would count as "undoing the drift". The warning text also leaks Rust debug formatting and names codes that this flavor never emits (see below).
Impact
A user who annotates or reformats the vendored line can't get back to the registry pin through any socket-patch command. Doing that is normal for a hand-maintained requirements.txt, and so is running a comment-stripping tool. vendor --revert exits 0 and status: "success", so scripts and CI treat the unvendor as done, while requirements.txt still installs the vendored wheel and .socket/vendor/pypi/<uuid>/ plus the ledger entry stay. (remove does exit 1 with vendor_revert_kept, so the two front doors disagree about the same state.) Installs keep working; nothing is unpatched. The harm is a stuck project and a misleading success.
Repro
This uses the in-repo vendoring fixture (tests/prebuilt_common::prepare_command, the same server e2e_vendor_pypi_build.rs::pip_requirements_vendor_fresh_checkout_no_index_and_revert uses), with real pip 24.0 on CPython 3.11 and six==1.16.0 installed in ./.venv. The .socket/manifest.json and blob are staged exactly like that test's stage_patch.
printf 'idna==3.7\nsix==1.16.0\n' > requirements.txt
socket-patch vendor --json --cwd . # applied: 1
cat requirements.txt
# idna==3.7
# ./.socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl # socket-patch vendor: six==1.16.0
# Any one of these edits:
sed -i 's/ # socket-patch vendor/ # socket-patch vendor/' requirements.txt # (a) one space fewer
sed -i '2s/$/ # CVE fix, do not bump/' requirements.txt # (b) user note
sed -i 's/ # socket-patch vendor.*//' requirements.txt # (c) comment stripped
socket-patch vendor --check --cwd .; echo $? # 0: "in sync"
socket-patch vendor --revert --cwd .; echo $? # 0
# Warning: requirements.txt: the vendor line for Some("requirements.txt:2") changed since vendoring; left untouched
# Warning: requirements.txt still references .socket/vendor/pypi/<uuid> after revert
# Warning: kept .socket/vendor/pypi/<uuid>: some recorded lock entries were left alone (see the vendor_lock_entry_drifted / vendor_lock_entry_removed warnings) … undo the drift (restore the vendored lock entries or re-vendor) and re-run `vendor --revert` to finish cleaning up
# Kept 1 drifted package: lock entries were re-resolved since vendoring, …
socket-patch vendor --json --cwd . # the suggested remedy
# (a)/(b): skipped already_vendored "artifact and lockfile wiring already in sync"; file unchanged
# (c): applied; appends a second line for the same wheel:
# ./.socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl
# ./.socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl # socket-patch vendor: six==1.16.0 (transitive)
socket-patch vendor --revert --cwd .; echo $?
# (a)/(b): same drift-keep, exit 0, forever
# (c): removes only the "(transitive)" line; the bare path line stays, `six==1.16.0` never comes back; exit 0
With (c) after the re-vendor, pip install --no-index -r requirements.txt accepts the duplicated wheel line and installs the patched build on pip 20.3.4, 24.0 and 26.2.1. So install works, and only the unwind is broken.
Expected vs actual
Message defects in the same path:
drift_warning formats rec.key (an Option<String>) with {:?}, so users see Some("requirements.txt:2"). The same {:?}-on-rec.key pattern is in pypi_uv.rs:847, pypi_pipenv.rs:468 and common.rs:806.
- The
vendor_artifact_kept hint points at vendor_lock_entry_drifted / vendor_lock_entry_removed, but the requirements flavor emits vendor_revert_line_drifted. The summary also says "lock entries were re-resolved", which isn't what happened here.
Matrix
| OS |
pip / Python |
Edit to the vendor line |
vendor --check |
vendor --revert |
re-vendor (remedy) |
Reproduces |
| Linux |
24.0 / CPython 3.11 |
(a) comment spacing |
0 (in sync) |
drift-keep, exit 0 |
already_vendored, no-op |
yes (3/3) |
| Linux |
24.0 / CPython 3.11 |
(b) user note after the marker |
0 |
drift-keep, exit 0 |
already_vendored, no-op |
yes (2/2) |
| Linux |
24.0 / CPython 3.11 |
(c) marker comment stripped |
0 |
drift-keep, exit 0 |
appends a duplicate (transitive) line; next revert strands the user's line |
yes (3/3) |
| Linux |
24.0 / CPython 3.11 |
none (control) |
0 |
byte-identical revert |
n/a |
no |
pip only matters for the install check (20.3.4 / 24.0 / 26.2.1 all install the duplicated file). The rewrite and revert logic is pip-independent and OS-independent, so macOS and Windows weren't probed.
First bad version: not bisected (main 9c43dfc; the latest release is v4.0.0).
Suspect code
crates/socket-patch-core/src/vendor/pypi_requirements.rs:415: revert finds the line with lines.iter().rposition(|l| l.trim() == new_line.trim()), which compares the whole line including the comment. Compare :900 (requirements_entry_in_use), which checks split_comment(&ll.text).0, and the --check / already_vendored path, which also treats the line as wired.
crates/socket-patch-core/src/vendor/pypi_requirements.rs:744 (vendor_line_marker(split_comment(old).0)) and the planner around :594 / :648: a bare wheel path line isn't recognised as the package's pin, so a re-vendor appends a (transitive) line.
crates/socket-patch-core/src/vendor/pypi_requirements.rs:482-489 (drift_warning, {:?} on rec.key).
Probe runs: none (OS-independent rewrite logic).
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
The vendored requirements.txt flavor decides whether a vendor line is still "ours" in two different ways:
vendor,vendor --checkand the Vendored requirements.txt after the user removes or bumps a vendored pin: the rescan re-adds the removed package as a "(transitive)" line (exit 0), or exits 1 forever after a bump, andscan --prunenever reverts the entry #786 in-use probe look only at the code part of the line (split_comment(..).0contains.socket/vendor/pypi/<uuid>/).vendor --revert(and soremove/rollback) needs the whole line, comment included, to equal the recordednewline (l.trim() == new_line.trim()).So any edit to the trailing
# socket-patch vendor: six==1.16.0comment leaves a project that installs the patched wheel fine but can never be reverted. The edit can be a changed space, a user's own note appended after it, or the comment stripped by a formatter.vendor --revertprintsvendor_revert_line_drifted, keeps the wheel and the ledger entry, and exits 0 withstatus: "success". Its remedy text says to "undo the drift (restore the vendored lock entries or re-vendor) and re-runvendor --revert". But:vendorreportsalready_vendored("artifact and lockfile wiring already in sync") and changes nothing. Re-runningvendor --revertdrift-keeps again, forever.vendordoesn't recognise the bare wheel path as six's pin. It appends a second wiring line for the same wheel,./…/six-1.16.0-py2.py3-none-any.whl # socket-patch vendor: six==1.16.0 (transitive). A latervendor --revertremoves only that appended line. The user's bare path line stays, the originalsix==1.16.0pin is never restored, and the run exits 0 again.Nothing tells the user which exact bytes would count as "undoing the drift". The warning text also leaks Rust debug formatting and names codes that this flavor never emits (see below).
Impact
A user who annotates or reformats the vendored line can't get back to the registry pin through any socket-patch command. Doing that is normal for a hand-maintained requirements.txt, and so is running a comment-stripping tool.
vendor --revertexits 0 andstatus: "success", so scripts and CI treat the unvendor as done, while requirements.txt still installs the vendored wheel and.socket/vendor/pypi/<uuid>/plus the ledger entry stay. (removedoes exit 1 withvendor_revert_kept, so the two front doors disagree about the same state.) Installs keep working; nothing is unpatched. The harm is a stuck project and a misleading success.Repro
This uses the in-repo vendoring fixture (
tests/prebuilt_common::prepare_command, the same servere2e_vendor_pypi_build.rs::pip_requirements_vendor_fresh_checkout_no_index_and_revertuses), with real pip 24.0 on CPython 3.11 andsix==1.16.0installed in./.venv. The.socket/manifest.jsonand blob are staged exactly like that test'sstage_patch.With (c) after the re-vendor,
pip install --no-index -r requirements.txtaccepts the duplicated wheel line and installs the patched build on pip 20.3.4, 24.0 and 26.2.1. So install works, and only the unwind is broken.Expected vs actual
vendor --revert", and the warning says to "restore the vendored lock entries or re-vendor". A line that pip reads identically, and thatvendor/vendor --check/ the Vendored requirements.txt after the user removes or bumps a vendored pin: the rescan re-adds the removed package as a "(transitive)" line (exit 0), or exits 1 forever after a bump, andscan --prunenever reverts the entry #786 in-use probe all treat as wired, should also be revertible. At minimum, the remedy the CLI prints has to lead to a finished revert.vendor --revertthat keeps everything shouldn't reportstatus: "success"with exit 0 whileremovereportspartialFailure/ exit 1 for the same drift-keep (CLI_CONTRACTvendor_revert_kept: "ANY drift-keep makes the run apartialFailure(exit 1)").vendorcan't repair. The stripped-comment case also gets a duplicate wiring line whose revert strands the user's line.Message defects in the same path:
drift_warningformatsrec.key(anOption<String>) with{:?}, so users seeSome("requirements.txt:2"). The same{:?}-on-rec.keypattern is inpypi_uv.rs:847,pypi_pipenv.rs:468andcommon.rs:806.vendor_artifact_kepthint points atvendor_lock_entry_drifted / vendor_lock_entry_removed, but the requirements flavor emitsvendor_revert_line_drifted. The summary also says "lock entries were re-resolved", which isn't what happened here.Matrix
vendor --checkvendor --revertvendor(remedy)already_vendored, no-opalready_vendored, no-op(transitive)line; next revert strands the user's linepip only matters for the install check (20.3.4 / 24.0 / 26.2.1 all install the duplicated file). The rewrite and revert logic is pip-independent and OS-independent, so macOS and Windows weren't probed.
First bad version: not bisected (main
9c43dfc; the latest release is v4.0.0).Suspect code
crates/socket-patch-core/src/vendor/pypi_requirements.rs:415: revert finds the line withlines.iter().rposition(|l| l.trim() == new_line.trim()), which compares the whole line including the comment. Compare:900(requirements_entry_in_use), which checkssplit_comment(&ll.text).0, and the--check/already_vendoredpath, which also treats the line as wired.crates/socket-patch-core/src/vendor/pypi_requirements.rs:744(vendor_line_marker(split_comment(old).0)) and the planner around:594/:648: a bare wheel path line isn't recognised as the package's pin, so a re-vendor appends a(transitive)line.crates/socket-patch-core/src/vendor/pypi_requirements.rs:482-489(drift_warning,{:?}onrec.key).Probe runs: none (OS-independent rewrite logic).