Repository navigation
Description
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
A Pipenv project often has a root requirements.txt exported by pipenv requirements (or pipenv lock -r on 2018) for Docker or plain-pip installs. Hosted mode rewires both files; that's been the case since the #333 fix. Vendored mode rewires only Pipfile.lock:
scan --mode vendored/vendorleavessix==1.16.0inrequirements.txtuntouched. It reportsstatus: success, exit 0, and emits no warning.vendor --checkthen reportsverified: 1, success.pip install -r requirements.txtinstalls the unpatched upstream six.vexsees the conflict and refuses to attest (patched_ref_unattributable, exit 1). Its remedy says "rewire both locks (re-runsocket-patch scan/vendor)", but re-running givesalready_vendoredand changes nothing. So the user can't get the project attested.- The hosted → vendored takeover is worse. Start from a hosted project where both files carry the hosted URL.
scan --mode vendoredreportsvendor_takeover_reverted_redirect"restored its upstream registry entry (Pipfile.lock, requirements.txt)", then vendors only Pipfile.lock. So the mode switch turns a patchedrequirements.txtback intosix==1.16.0from PyPI, and still reports success.
The vendored flavor router detect_pypi_flavor (crates/socket-patch-core/src/vendor/pypi.rs:248) returns PypiFlavor::Pipenv as soon as Pipfile.lock exists (:333). requirements.txt only counts as a competing install source on the standalone-pylock branch (:286), so the pypi_multiple_lockfiles warning (:312) never fires for Pipfile.lock + requirements.txt.
Impact
Teams that build containers with pipenv requirements > requirements.txt && pip install -r requirements.txt ship the unpatched package after a successful vendored run, and vendor --check (the CI gate) passes. A user switching an existing hosted project to vendored loses the patch on the pip path without any signal except a later VEX refusal.
Repro (Linux; real Pipenv; local mock patch API serving a patched six 1.16.0 wheel)
mkdir app && cd app
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
six = "==1.16.0"
idna = "==3.7"
[requires]
python_version = "3.12"
EOF
pipenv lock
pipenv requirements > requirements.txt # 2018.11.26: pipenv lock -r > requirements.txt
socket-patch scan --mode vendored --yes --json # status success, exit 0, no warning
grep six requirements.txt # six==1.16.0; python_version >= ... (unchanged)
grep -c socket/vendor Pipfile.lock # 1
socket-patch vendor --check --json # success, verified: 1
uv venv rv && uv pip install -p rv/bin/python -r requirements.txt
rv/bin/python -c "import six; print(getattr(six,'SOCKET_PATCHED','UNPATCHED'))" # UNPATCHED
pipenv sync && pipenv run python -c "import six; print(six.SOCKET_PATCHED)" # 1
socket-patch vex --product pkg:pypi/app@1.0.0 # exit 1: patched_ref_unattributable, "re-run scan / vendor"
socket-patch scan --mode vendored --yes --json # already_vendored; requirements.txt still unpatched
# Takeover variant (fresh copy of the same project)
socket-patch scan --mode hosted --yes --json # rewrittenFiles: [Pipfile.lock, requirements.txt]
socket-patch scan --mode vendored --yes --json # success; vendor_takeover_reverted_redirect (Pipfile.lock, requirements.txt)
cat requirements.txt # six==1.16.0 ; python_version ... <- hosted pin goneEvery cell below was reproduced twice.
Expected vs actual
- Expected: one of these:
- Vendored wires every install source that pins the package, as hosted does. Plain
requirements.txtvendoring is supported (docs/ecosystems.md: "pipenv … and requirements.txt"), and works on the same file without a Pipfile.lock. - Or, at minimum, the documented warning. CLI_CONTRACT.md:1179: "
pypi_multiple_lockfiles… a sibling lockfile of another package manager will still install UNPATCHED bytes; names the wired winner + the ignored locks". Thedetect_pypi_flavordoc comment promises this "LOUD" warning, because such files otherwise "go stale-but-valid, which is otherwise invisible". - A takeover should never leave a file less patched than it found it.
- Vendored wires every install source that pins the package, as hosted does. Plain
- Actual: silent success,
vendor --checkgreen, the pip path unpatched. The takeover actively unpatchesrequirements.txt, and the VEX remedy text can't be followed.
OS × version (Linux, main 045d7ec)
| Pipenv | vendored skips requirements.txt, no warning | vendor --check green |
pip install -r unpatched |
vex refuses | hosted → vendored unpatches requirements.txt |
|---|---|---|---|---|---|
2018.11.26 (py3.8, lock -r) |
✅ repro 2/2 | ✅ | ✅ | ✅ | not run |
| 2023.12.1 (py3.12) | ✅ repro 2/2 | ✅ | ✅ | ✅ | not run |
| 2026.8.0 (py3.12) | ✅ repro 2/2 | ✅ | ✅ | ✅ | ✅ repro |
| macOS / Windows | not probed (pure routing logic, no path handling) |
Control: the same requirements.txt with no Pipfile.lock beside it is vendored correctly (./.socket/vendor/pypi/<uuid>/six-….whl ; <markers> # socket-patch vendor: six==1.16.0).
First bad version
Not bisected. v4.0.0 can't vendor against the mock (vendor_fetch_unverifiable), so I couldn't compare it. The routing precedence predates the #503 / #572 changes.
Suspect code
crates/socket-patch-core/src/vendor/pypi.rs:248detect_pypi_flavor: aPipfile.lock(likewisepoetry.lock/pdm.lock, untested here) wins without countingrequirements.txtas a competing install source (:286is the only place it's added topresent).crates/socket-patch-cli/src/commands/vendor.rs:2527: the takeover reverts the redirect in every file it touched, before the flavor router decides which single file to vendor.
Related, but different: #567 (hosted, -r include), #333 (hosted, closed), #328 / #503 (vendored → hosted takeover, which does rewire both files).
Backlog review — 2026-10-08
Priority: P1 → P2. The sibling requirements/Pipenv ambiguity is real, but #1044 added a warning and vendor/VEX checking rejects the mixed state. Retain the policy fix at P2.
Activity
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). A normal Pipenv project can install through its exported requirements.txt. Vendoring/takeover must keep that declared install path patched too.
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 9, 2026
[agent] Claiming for v5 blocker burn-down (shared root cause: vendored PyPI flavor routing wires only Pipfile.lock and never rewires a sibling exported requirements.txt). Branch: agent/v5-pipenv-vendor-sibling-reqs. Claim-ID: 2026-10-09T16:41:34Z-55133a
Vendor Pipenv's exported requirements.txt too
Test that co-wired Pipenv files are not contested
mikolalysenko commentedon Oct 9, 2026
[agent] Took over the stale claim on #1309 (Claim-ID 2026-10-09T18:21:01Z-7c41e9). First slice pushed: a fresh vendored Pipenv run now wires the exported
requirements.txtalong withPipfile.lock, revert restores both byte for byte, and discovery no longer reports the two files as contesting each other. Still to do (listed in the PR): the takeover e2e, re-running over projects vendored before this change, the superseding path, and the uv/pylock lanes.Generated by Claude Code
WIP: Fix vendored Pipenv sibling requirements.txt (#612)
Wire Pipenv's exported requirements in vendor
Pin the both-files takeover contract (#612)
Merge the parallel #612 attempt into this branch
Merge origin/main into agent/v5-pipenv-vendor-sibling-reqs
Harden the in-sync Pipenv export wiring and its revert (#612)
Vendor uv's exported requirements.txt too
Wire requirements.txt beside a pylock too