Repository navigation
After pipenv uninstall of a vendored package from a named category on Pipenv 2022/2023, scan --prune, vendor --revert, remove and rollback keep it as "drifted", so vendor --check stays red and its scan --prune remedy loops #1142
Description
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
A package is vendored in a Pipenv named category (for example [docs]), and it's the only package in that category. When the user runs pipenv uninstall six --categories docs, Pipenv 2022.x and 2023.x drop the now-empty docs key from Pipfile.lock entirely. Pipenv 2026 keeps "docs": {}.
On main, the vendored Pipenv revert reads a missing category key as drift, not as removal. So after the uninstall:
vendor --checkexits 1. It says "dependency removed … runsocket-patch scan --mode vendored --pruneto revert the vendored entry".scan --mode vendored --pruneexits 0 but lists the entry undergc.keptVendoredEntries. The artifact and the ledger entry stay.vendor --checkis still red afterwards.vendor --revertexits 0 and keeps the entry: "undo the drift (restore the vendored lock entries or re-vendor) and re-runvendor --revert".remove pkg:pypi/six@1.16.0androllback pkg:pypi/six@1.16.0exit 1: "re-runscan --mode vendoredto normalize, then remove again". A re-scan is a no-op, because six is no longer in the project.
The user can't "undo the drift" without reinstalling the package they just removed. The only way to get vendor --check green again is to delete .socket/vendor/state.json entries and the artifact by hand.
Impact
Once a dependency is uninstalled this way, a CI gate on vendor --check stays red permanently. Every remedy the CLI prints is a no-op or loops. The dead wheel and ledger entry are never reclaimed. Nothing installs unpatched bytes, so this isn't a security issue, but the CI gate is broken.
Expected vs actual
- Expected: the revert already retires a record whose entry a relock dropped from a category that still exists. It returns
vendor_lock_entry_relockedand says so in its comment: "A relock dropped the entry (pipenv uninstall <pkg>, a Pipfile edit +pipenv lock) … retire the record rather than keep the orphan forever" (crates/socket-patch-core/src/vendor/pypi_pipenv.rs:616-626).docs/usage.md:223says--pruneremoves "records for dependencies that left the project".vendor --checkitself sends the user toscan --prune. When the uninstall also removed the category key, the record should be retired in the same way. - Actual: with the whole category gone, the record is treated as drift and kept forever.
Repro (Linux, main 823810a, real Pipenv 2023.12.1 on py3.11, local mock patch API serving a patched six 1.16.0 wheel)
mkdir p && cd p && git init -q
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
idna = "==3.7"
[docs]
six = "==1.16.0"
[requires]
python_version = "3.11"
EOF
pipenv lock
socket-patch scan --mode vendored --yes $MOCK_ARGS # docs:six -> ./.socket/vendor/pypi/<uuid>/six-…whl
git add -A && git commit -qm vendored
pipenv uninstall six --categories docs # Pipfile keeps an empty [docs]; Pipfile.lock loses the "docs" key
socket-patch vendor --check; echo $? # 1: "dependency removed … run `scan --mode vendored --prune`"
socket-patch scan --mode vendored --yes --prune $MOCK_ARGS --json | jq .gc.keptVendoredEntries # ["pkg:pypi/six@1.16.0"]
socket-patch vendor --check; echo $? # still 1
socket-patch vendor --revert # "Pipfile.lock entry for Some(\"docs:six\") changed since vendoring; left untouched" … Kept 1 drifted package
socket-patch remove pkg:pypi/six@1.16.0 --yes; echo $? # 1: drift-kept, "re-run scan --mode vendored to normalize"The same shape fails if [docs] is edited by hand and then pipenv lock runs. Control: when six is removed from [docs] on Pipenv 2026.8.0 (which keeps "docs": {}), or from [packages] on any version (default always stays), scan --prune reverts the entry, the artifact is removed and vendor --check exits 0.
OS × version
| OS | Pipenv | result |
|---|---|---|
| Linux | 2022.12.19 | fail (2/2 runs) |
| Linux | 2023.12.1 | fail (2/2 runs; remove / rollback also exit 1) |
| Linux | 2026.8.0 | pass (Pipenv keeps the empty docs key) |
| macOS / Windows | — | untested (lock-shape bug, OS-independent) |
First bad: not in v4.0.0, which has no vendor --check, and whose scan --prune never judged Pipenv entries. On main, #1050 routes the prune GC and the check's "dependency removed" remedy through Discovery::vendor_entry_in_use. That verdict correctly returns Some(false) here, but the revert then drift-keeps the entry.
Suspect code
crates/socket-patch-core/src/vendor/pypi_pipenv.rs:600-602 (revert_pipenv): let Some(map) = lock.get_mut(section)… else { warnings.push(drifted()); continue; }. A missing category is reported as vendor_lock_entry_drifted. The missing-entry arm a few lines below (:616-626) retires a Rewritten record that has an original. The missing-section case could take the same arm, perhaps only when the section isn't _meta and the lock still parses as a pipfile-spec 6 lock. The unit test revert_drift_skips_missing_section_entry_new_and_missing_original (:1804) currently pins the drift behaviour for a deleted section.
Related, but in other backends with separate code: #1132 (bun.lockb) and #1140 (uv).
Probe runs: none. This is a lock-shape bug that doesn't depend on the OS, and probe branches are currently blocked for this routine.
Activity
pipenv: 2026-10-08 15:39Z run ledger (#1142)
mikolalysenko commentedon Oct 8, 2026
[agent] Triaged: priority:p1 (Pipenv). No duplicate or open PR. Confirmed on main: in
crates/socket-patch-core/src/vendor/pypi_pipenv.rs, the revert's record loop handles a missing entry in an existing category by retiring it (vendor_lock_entry_relocked). But a missing category (lock.get_mut(section)isNone) falls intodrifted()before that arm runs. Pipenv 2022/2023 drop the empty category key, so the record is kept forever. Related, but a different code path: #1140 (uv) and #1132 (Bun) have the same "removed dependency is read as drift" symptom.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Claiming this issue (with #1132, #1140; shared root cause: the bun.lockb, uv and Pipenv vendored revert backends map a lock entry the package manager removed to
vendor_lock_entry_driftedinstead of the sharedvendor_lock_entry_removedarm the npm-family text backends use, so every unwind keeps the entry). Branch: agent/fix-vendored-removed-dep-drift. Claim-ID: 2026-10-08T16:20:32Z-8b1b90Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Draft PR: #1147
Generated by Claude Code
Retire Pipenv records whose category was dropped
Retire Pipenv records whose category was dropped