Skip to content

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

Description

[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) failed pypi_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) skipped package_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.six file 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions