You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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, and scan --prune never reverts the entry #786
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
#541 / #543 taught vendored_ledger_supplement to skip vendor-ledger entries whose dependency has left the lock: no re-vendor, a vendor_ledger_entry_unwired warning, and scan --prune reverts them with exit 0. The filter asks dispatch_in_use_one, which only has probes for npm and cargo. For pypi it returns None ("can't tell"), and the supplement treats that as "still wired". So on a requirements.txt project, a vendored entry is always rediscovered after the user removes or bumps the package. Two outcomes follow:
Removal (six==… line deleted, package uninstalled): the next scan --mode vendored re-vendors six@1.16.0 and appends./.socket/vendor/pypi/<uuid>/six-1.16.0-…whl # socket-patch vendor: six==1.16.0 (transitive) to requirements.txt. It exits 0, with no warning. A dependency the user deliberately removed goes back into their requirements file, and a fresh pip install -r requirements.txt installs it again. The ledger also keeps the stale requirements.txt:1 "rewritten" wiring next to the new "added" one.
Bump (six==1.16.0 → six==1.17.0, pip install run): every later scan --mode vendored exits 1 (partial_failure, pypi_requirement_not_pinned: "six is not pinned to ==1.16.0; pin it exactly or use agent mode"), which is the Vendored vlt scan exits 1 after the patched dependency is upgraded or uninstalled, and even scan --prune exits 1 while it reverts the stale entry #541 symptom. --prune doesn't help: gc.revertedVendoredEntries is [] and it still exits 1, on every run. --dry-run --prune previews already_vendored, exit 0 and revertableVendoredEntries: [], so the preview disagrees with the wet run.
In both cases scan --prune (the documented reconcile) never reverts the entry, so the dead uuid dir and wheel stay committed.
Impact
Removal: socket-patch silently re-adds a removed (patched) dependency to requirements.txt. Anyone who drops a package from a vendored pip project gets it back on the next scheduled scan, and CI stays green.
Bump: a scheduled scan --mode vendored goes red permanently after a routine upgrade of a vendored package, with misleading advice. The only way out is a manual socket-patch vendor --revert (that works: it skips the drifted line as vendor_revert_line_drifted and removes .socket/).
Repro (Linux, main 045d7ec, pip 26.2.1 / CPython 3.13)
This uses a local mock. Patch discovery (batch / by-package) answers only for six@1.16.0, the view/<uuid> route serves a one-line marker patch on the real installed six.py, and the vendored artifact comes from prebuilt_common::mount_view_from_source. It ran through a throwaway, uncommitted test driver.
python3.13 -m venv venv && venv/bin/pip install pip==26.2.1 six==1.16.0
export VIRTUAL_ENV=$PWD/venv
printf'six==1.16.0\nidna==3.7\n'> requirements.txt
F="--json --yes --api-url $MOCK --api-token fake --org test-org --vendor-url $MOCK --patch-server-url $MOCK"
socket-patch scan --mode vendored $F# exit 0; line 1 -> ./.socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl # socket-patch vendor: six==1.16.0# Case A: the user removes sixprintf'idna==3.7\n'> requirements.txt && venv/bin/pip uninstall -y six
socket-patch scan --mode vendored $F# exit 0, vendor event "applied"; requirements.txt gains the "(transitive)" six line
socket-patch scan --mode vendored --prune $F# exit 0, gc.revertedVendoredEntries: [], the line stays
python3.13 -m venv fresh && fresh/bin/pip install -r requirements.txt # installs six 1.16.0 (the patched wheel)# Case B (fresh project): the user bumps sixprintf'six==1.17.0\nidna==3.7\n'> requirements.txt && venv/bin/pip install six==1.17.0
socket-patch scan --mode vendored $F# exit 1, pypi_requirement_not_pinned
socket-patch scan --mode vendored --prune $F# exit 1, gc.revertedVendoredEntries: [] (and on every later run)
socket-patch scan --mode vendored --prune --dry-run $F# exit 0, "already_vendored", revertableVendoredEntries: []
Both cases reproduced on two separate runs.
Expected vs actual
CLI_CONTRACT.md, scan --vendor paragraph: "an entry the lockfile in-use probe (the one --prune reverts by) proves unwired, because the dependency was upgraded or removed, is NOT discovered and so is never re-vendored. A run without a non-hosted --prune reports it through the run-level vendor_ledger_entry_unwired warning; a --prune run reverts it in its GC and exits 0." The scan --prune paragraph, leg (b): "EVERY ledger entry whose dependency is no longer in the lockfile graph is reverted … a missing or undeterminable lockfile keeps the entry".
Here the lockfile (requirements.txt) is present and readable, and the dependency is plainly gone from it, or pinned to another version. Expected: no re-vendor, vendor_ledger_entry_unwired, exit 0; and --prune reverts the entry, exit 0. Actual: the removed package gets re-added (A), or the run exits 1 forever (B), and --prune reverts nothing.
Keeping an entry when the probe can't decide is a reasonable fail-safe for the GC. The problem is that the discovery supplement turns that "keep" into "re-discover and re-vendor", which actively re-wires the project.
OS × version
OS
pip / Python
Case A (removal)
Case B (bump)
Linux
26.2.1 / 3.13
reproduces
reproduces
macOS / Windows
—
not probed (OS-independent: discovery and text logic, no path handling involved)
not probed
First bad version
Not bisected. The npm- and cargo-only probe set comes from #543 (pinned by the unit test in_use_probe_is_none_for_unprobed_ecosystems, crates/socket-patch-cli/src/commands/vendor.rs:5374, which lists pypi). PyPI has never had a probe.
Suspect code
crates/socket-patch-cli/src/commands/vendor.rs:260dispatch_in_use_one: it has no "pypi" arm, so it returns None.
crates/socket-patch-cli/src/commands/scan/discovery.rs:205vendored_ledger_supplement: it treats None the same as Some(true) and supplements the entry.
The prune leg (vendor.rs:3494) has the same None → keep behaviour.
The other PyPI vendored flavors (Poetry, Pipenv, uv, Hatch, PDM) go through the same dispatch and probably behave the same way. I haven't verified that; it's for their routines.
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
#541 / #543 taught
vendored_ledger_supplementto skip vendor-ledger entries whose dependency has left the lock: no re-vendor, avendor_ledger_entry_unwiredwarning, andscan --prunereverts them with exit 0. The filter asksdispatch_in_use_one, which only has probes for npm and cargo. Forpypiit returnsNone("can't tell"), and the supplement treats that as "still wired". So on a requirements.txt project, a vendored entry is always rediscovered after the user removes or bumps the package. Two outcomes follow:six==…line deleted, package uninstalled): the nextscan --mode vendoredre-vendorssix@1.16.0and appends./.socket/vendor/pypi/<uuid>/six-1.16.0-…whl # socket-patch vendor: six==1.16.0 (transitive)to requirements.txt. It exits 0, with no warning. A dependency the user deliberately removed goes back into their requirements file, and a freshpip install -r requirements.txtinstalls it again. The ledger also keeps the stalerequirements.txt:1"rewritten" wiring next to the new "added" one.six==1.16.0→six==1.17.0,pip installrun): every laterscan --mode vendoredexits 1 (partial_failure,pypi_requirement_not_pinned: "six is not pinned to ==1.16.0; pin it exactly or use agent mode"), which is the Vendored vlt scan exits 1 after the patched dependency is upgraded or uninstalled, and evenscan --pruneexits 1 while it reverts the stale entry #541 symptom.--prunedoesn't help:gc.revertedVendoredEntriesis[]and it still exits 1, on every run.--dry-run --prunepreviewsalready_vendored, exit 0 andrevertableVendoredEntries: [], so the preview disagrees with the wet run.In both cases
scan --prune(the documented reconcile) never reverts the entry, so the dead uuid dir and wheel stay committed.Impact
requirements.txt. Anyone who drops a package from a vendored pip project gets it back on the next scheduled scan, and CI stays green.scan --mode vendoredgoes red permanently after a routine upgrade of a vendored package, with misleading advice. The only way out is a manualsocket-patch vendor --revert(that works: it skips the drifted line asvendor_revert_line_driftedand removes.socket/).Repro (Linux, main
045d7ec, pip 26.2.1 / CPython 3.13)This uses a local mock. Patch discovery (
batch/by-package) answers only forsix@1.16.0, theview/<uuid>route serves a one-line marker patch on the real installedsix.py, and the vendored artifact comes fromprebuilt_common::mount_view_from_source. It ran through a throwaway, uncommitted test driver.Both cases reproduced on two separate runs.
Expected vs actual
CLI_CONTRACT.md,
scan --vendorparagraph: "an entry the lockfile in-use probe (the one--prunereverts by) proves unwired, because the dependency was upgraded or removed, is NOT discovered and so is never re-vendored. A run without a non-hosted--prunereports it through the run-levelvendor_ledger_entry_unwiredwarning; a--prunerun reverts it in its GC and exits 0." Thescan --pruneparagraph, leg (b): "EVERY ledger entry whose dependency is no longer in the lockfile graph is reverted … a missing or undeterminable lockfile keeps the entry".Here the lockfile (
requirements.txt) is present and readable, and the dependency is plainly gone from it, or pinned to another version. Expected: no re-vendor,vendor_ledger_entry_unwired, exit 0; and--prunereverts the entry, exit 0. Actual: the removed package gets re-added (A), or the run exits 1 forever (B), and--prunereverts nothing.Keeping an entry when the probe can't decide is a reasonable fail-safe for the GC. The problem is that the discovery supplement turns that "keep" into "re-discover and re-vendor", which actively re-wires the project.
OS × version
First bad version
Not bisected. The npm- and cargo-only probe set comes from #543 (pinned by the unit test
in_use_probe_is_none_for_unprobed_ecosystems,crates/socket-patch-cli/src/commands/vendor.rs:5374, which listspypi). PyPI has never had a probe.Suspect code
crates/socket-patch-cli/src/commands/vendor.rs:260dispatch_in_use_one: it has no"pypi"arm, so it returnsNone.crates/socket-patch-cli/src/commands/scan/discovery.rs:205vendored_ledger_supplement: it treatsNonethe same asSome(true)and supplements the entry.vendor.rs:3494) has the sameNone→ keep behaviour.The other PyPI vendored flavors (Poetry, Pipenv, uv, Hatch, PDM) go through the same dispatch and probably behave the same way. I haven't verified that; it's for their routines.