Repository navigation
Description
[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.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,
vendorreportsalready_vendored("artifact and lockfile wiring already in sync") and changes nothing. Re-runningvendor --revertdrift-keeps again, forever. - When the comment was stripped,
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 --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 0With (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
- Expected: CLI_CONTRACT.md says a drift-kept entry is finished by "undo the drift and re-run
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)"). - Actual: comment-only edits put the entry into a permanent drift-keep that
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.- The
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
| 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 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).
Backlog review — 2026-10-08
Priority: P1 → P2. Editing the vendor marker comment prevents automated revert, but the artifact is retained. Real recovery issue with a manual-edit trigger.
Activity
pip: run 2026-10-07T02:19Z (filed #977)
mikolalysenko commentedon Oct 7, 2026
[agent] Triaged as
priority:p1(pip / requirements.txt). Confirmed on main9c43dfc:pypi_requirements.rs:415matches the recorded vendor line by whole-line equality (comment included), while the--check/already_vendored/ in-use paths key onsplit_comment(..).0, so a comment-only edit is "wired" for one side and "drifted" for revert. Not a duplicate of #867 (that one is about a-rinclude still referencing the wheel) and no open PR covers it.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Structural context from the architecture audit (ecosystems and formats): forward and revert recognizing "our" wiring by different predicates is not limited to requirements.txt. On
9c43dfc:gemfile_text.contains(©_rel); the revert needs an exact line match. Gem also reverts its coupled records non-atomically, so a trailing comment on the Gemfile line leavesGemfile.lockrestored to the registry and the Gemfile still onpath:, and the revert drift-keeps forever. That is Vendored gem revert restores Gemfile.lock but leaves the Gemfile path: line when that line carries a trailing comment, and no re-run can finish it #988.This issue can still be fixed on its own first.
Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 triage: P3, not a release blocker. The user-edited Socket bookkeeping comment case stays as P3 recovery work; it is not the ordinary unmodified-lockfile path.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 29, 2026
[agent] Claiming this issue (shared root cause: vendored requirements.txt revert finds its line by whole-line equality, comment included, while vendor/--check/in-use key on the code part). Branch: agent/fix-pypi-requirements-revert-comment. Claim-ID: 2026-10-29T11:29:54Z-adcf8e
Generated by Claude Code
mikolalysenko commentedon Oct 29, 2026
[agent] Draft PR: #1825
Generated by Claude Code
Revert requirements lines with an edited comment
Test pip revert after the vendor comment changes
Keep edited vendor lines paired and inventoried
mikolalysenko commentedon Oct 29, 2026
[agent] #1825 fixes the stuck revert and the duplicate re-vendor line for all three edits (respaced, note appended, stripped), with unit and real-pip e2e tests. It uses
Refs, notFixes:vendor --revertstill exits 0 withstatus: successon a drift-keep whileremoveexits 1, and the summary still says "lock entries were re-resolved". Both are shared by every vendored ecosystem, so changing them is a cross-ecosystem contract call, and they are left open here. The{:?}key formatting was already fixed on main (#1608).Generated by Claude Code
Prefer the tagged line when pairing a revert
mikolalysenko commentedon Oct 29, 2026
[agent] #1825 merged: comment-only edits to a vendored requirements.txt line no longer block
vendor --revert/remove/rollback, and re-vendoring no longer duplicates the line. Two cross-ecosystem parts are left, and they need a maintainer decision on the contract: whethervendor --revertshould exit 1 withpartialFailureon a drift-keep, asremovedoes, and the generic "lock entries were re-resolved" summary wording. Releasing the claim and labelingagent:needs-humanfor that decision.Generated by Claude Code
mikolalysenko commentedon Oct 29, 2026
[agent] pip bug-hunt re-triage (ledger #309): the pip-side part of this issue is verified fixed on main
b291caa(#1825) with real pip. I'm leaving the issue open for the pendingagent:needs-humandecision on drift-keep exit codes.Setup:
idna==3.7+six==1.16.0,scan --mode vendored --yes, then each edit to the six vendor comment, thenvendor --checkand the revert:vendor --check# socket-patch vendor:)vendor --revert0, "Reverted 2 vendored packages"… # pinned by sec team)vendor --revert0vendor --revert0scan --mode vendoredagainpip install -rgives patched six and idnasix==1.16.0 # keep,vendor:respaced plus# editedremove pkg:pypi/six@1.16.00six==1.16.0 # keep\r; idna stays vendoredrollback0#socket-patch vendor:plus# secpip install -rgives patched six and idna)vendor --revert0Generated by Claude Code