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 Pipenv never picks up a superseding patch: re-vendor to a new uuid fails with pypi_pipenv_source_already_exists (lock-only) or a false package_not_installed (venv present), exit 1 #769
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
This is the Pipenv lane of the #765 family. #765 / PR #766 fix only requirements.txt, #742 covers uv and #650 covers Hatch. PR #766's description lists Pipenv among the vendored PyPI writers with "the same gap" but leaves it as an unfiled follow-up, so this issue tracks it.
A Pipenv project vendored at patch uuid A for pkg:pypi/six@1.16.0 never moves to a newer patch B. Both scan --mode vendored (once the API offers only B) and get <B> --mode vendored download B and report it as superseding A (updates[] / download.patches[].oldUuid = A, human [fetch] … (replacing 5a6b7c8d)). Then the vendor step refuses, and the run exits 1 (partial_failure). Pipfile.lock, .socket/vendor/pypi/<A>/ and the ledger all stay on A. Meanwhile --dry-run previews would_revendor + oldUuid and exits 0.
How it fails depends on whether the Pipenv venv exists:
shape
vendor event
lock-only checkout (no venv)
failedpypi_pipenv_source_already_exists: "Pipfile.lock already routes default.six through .socket/vendor/pypi/5a6b7c8d-… (an earlier socket-patch vendor); run socket-patch vendor --revert for it and re-vendor"
venv present (installed by pipenv sync from the vendored wheel A)
skippedpackage_not_installed: "no installed package found on disk". This is false: six is installed, and pipenv run python -c 'import six' loads patch A
Impact
Vendored Pipenv users can't receive an updated patch (a fixed patch, or one covering more CVEs). The scheduled or CI scan that should roll them forward fails every time instead. In the venv-present case the error message (package_not_installed) points away from the real cause. The committed wheel stays on the old patch until someone runs vendor --revert and re-vendors by hand. That workaround does work: vendor --revert → scan --mode vendored wires B, and pipenv sync on a fresh venv installs B.
Expected vs actual
crates/socket-patch-cli/CLI_CONTRACT.md, the scan --vendor paragraph: "A package the ledger holds at an older patch uuid is still re-vendored automatically when discovery selects the newer patch (its old uuid dir is removed — vendor_stale_artifact_removed)". The same paragraph says a downloaded record carrying oldUuid is "the re-vendor the vendor step then performs".
Expected: Pipfile.lock's default.sixfile ref is rewired to ./.socket/vendor/pypi/<B>/six-1.16.0-py2.py3-none-any.whl (hash updated, the recorded pre-vendor original carried forward so vendor --revert / rollback stay byte-exact), <A>/ is removed, the ledger moves to B, and the run exits 0, as --dry-run previews.
Actual: exit 1 with one of the two events above. Nothing changes.
Repro (main 045d7ec, Linux, real Pipenv)
The mock is a local stand-in for the Socket API and patch server: batch / by-package / view / package-grant / wheel routes for two patch records that append SOCKET_PATCHED = 'A' or 'B' to the real six.py, plus a SOCKET_PYPI_JSON_API forwarder. The record the mock offers is switched from A to B between steps. Shapes match the repo's vex_pypi_real_common fixture.
sp(){ socket-patch "$@" --api-url $M --api-token fake --org test-org --patch-server-url $M; }
printf'[[source]]\nurl = "https://pypi.org/simple"\nverify_ssl = true\nname = "pypi"\n\n[packages]\nsix = "==1.16.0"\n'> Pipfile
pipenv lock && git init -q && git add -A && git commit -qm init
# API offers patch A
sp scan --mode vendored --yes --json --cwd .# success, vendors A
git add -A && git commit -qm vendored-A
pipenv sync && pipenv run python -c 'import six; print(six.SOCKET_PATCHED)'# A# API now offers only patch B (same purl)
sp scan --mode vendored --yes --json --dry-run --cwd .# vendor.patches: would_revendor, oldUuid A, exit 0
sp scan --mode vendored --yes --json --cwd .# exit 1, skipped package_not_installed
sp get <uuidB> --mode vendored --yes --json --cwd .# same
pipenv --rm
sp scan --mode vendored --yes --json --cwd .# exit 1, failed pypi_pipenv_source_already_exists
git status --short # clean: lock + .socket still on A
Control: a re-scan while the API still offers A gives already_vendored (exit 0).
OS × version
OS
Pipenv
venv present
lock-only
dry-run
Linux
2018.11.26 (py3.8)
fail (package_not_installed)
fail (pypi_pipenv_source_already_exists)
would_revendor
Linux
2022.12.19 (py3.11)
fail
fail
would_revendor
Linux
2023.12.1 (py3.11)
fail
fail
would_revendor
Linux
2026.8.0 (py3.11)
fail
fail
would_revendor
macOS / Windows
any
untested (the code path isn't OS-specific)
Each cell reproduced at least twice. Pipenv 7–11 is out of scope (vendored is refused there, as documented).
Suspect code
crates/socket-patch-core/src/vendor/pypi_pipenv.rs:220-232: the "Ours, but a STALE patch generation" arm refuses outright instead of planning an in-place rewire (the requirements.txt equivalent is preflight_requirements → the new RequirementsTarget::Rewire in PR Fix vendored requirements.txt re-vendor to a superseding patch (#765) #766).
crates/socket-patch-cli/src/commands/vendor.rs:2918-2934: with the venv installed from vendored wheel A, the crawler's copy doesn't match the record's pristine beforeHash. The purl is neither in vendored_installs nor resolved through the lock-only fallback, so it reaches the generic "no installed package found on disk" branch instead of fetching the pristine wheel (which the lock-only path does) or naming the vendored artifact.
No probe runs: macOS / Windows probes are still blocked by branch deletion through the git proxy (ledger #313).
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
This is the Pipenv lane of the #765 family. #765 / PR #766 fix only requirements.txt, #742 covers uv and #650 covers Hatch. PR #766's description lists Pipenv among the vendored PyPI writers with "the same gap" but leaves it as an unfiled follow-up, so this issue tracks it.
A Pipenv project vendored at patch uuid A for
pkg:pypi/six@1.16.0never moves to a newer patch B. Bothscan --mode vendored(once the API offers only B) andget <B> --mode vendoreddownload B and report it as superseding A (updates[]/download.patches[].oldUuid= A, human[fetch] … (replacing 5a6b7c8d)). Then the vendor step refuses, and the run exits 1 (partial_failure). Pipfile.lock,.socket/vendor/pypi/<A>/and the ledger all stay on A. Meanwhile--dry-runpreviewswould_revendor+oldUuidand exits 0.How it fails depends on whether the Pipenv venv exists:
failedpypi_pipenv_source_already_exists: "Pipfile.lock already routes default.six through .socket/vendor/pypi/5a6b7c8d-… (an earlier socket-patch vendor); runsocket-patch vendor --revertfor it and re-vendor"pipenv syncfrom the vendored wheel A)skippedpackage_not_installed: "no installed package found on disk". This is false: six is installed, andpipenv run python -c 'import six'loads patch AImpact
Vendored Pipenv users can't receive an updated patch (a fixed patch, or one covering more CVEs). The scheduled or CI scan that should roll them forward fails every time instead. In the venv-present case the error message (
package_not_installed) points away from the real cause. The committed wheel stays on the old patch until someone runsvendor --revertand re-vendors by hand. That workaround does work:vendor --revert→scan --mode vendoredwires B, andpipenv syncon a fresh venv installs B.Expected vs actual
crates/socket-patch-cli/CLI_CONTRACT.md, thescan --vendorparagraph: "A package the ledger holds at an older patch uuid is still re-vendored automatically when discovery selects the newer patch (its old uuid dir is removed —vendor_stale_artifact_removed)". The same paragraph says adownloadedrecord carryingoldUuidis "the re-vendor the vendor step then performs".default.sixfileref is rewired to./.socket/vendor/pypi/<B>/six-1.16.0-py2.py3-none-any.whl(hash updated, the recorded pre-vendor original carried forward sovendor --revert/ rollback stay byte-exact),<A>/is removed, the ledger moves to B, and the run exits 0, as--dry-runpreviews.Repro (main
045d7ec, Linux, real Pipenv)The mock is a local stand-in for the Socket API and patch server: batch / by-package / view / package-grant / wheel routes for two patch records that append
SOCKET_PATCHED = 'A'or'B'to the realsix.py, plus aSOCKET_PYPI_JSON_APIforwarder. The record the mock offers is switched from A to B between steps. Shapes match the repo'svex_pypi_real_commonfixture.Control: a re-scan while the API still offers A gives
already_vendored(exit 0).OS × version
package_not_installed)pypi_pipenv_source_already_exists)would_revendorwould_revendorwould_revendorwould_revendorEach cell reproduced at least twice. Pipenv 7–11 is out of scope (vendored is refused there, as documented).
Suspect code
crates/socket-patch-core/src/vendor/pypi_pipenv.rs:220-232: the "Ours, but a STALE patch generation" arm refuses outright instead of planning an in-place rewire (the requirements.txt equivalent ispreflight_requirements→ the newRequirementsTarget::Rewirein PR Fix vendored requirements.txt re-vendor to a superseding patch (#765) #766).crates/socket-patch-cli/src/commands/vendor.rs:2918-2934: with the venv installed from vendored wheel A, the crawler's copy doesn't match the record's pristinebeforeHash. The purl is neither invendored_installsnor resolved through the lock-only fallback, so it reaches the generic "no installed package found on disk" branch instead of fetching the pristine wheel (which the lock-only path does) or naming the vendored artifact.No probe runs: macOS / Windows probes are still blocked by branch deletion through the git proxy (ledger #313).