Repository navigation
Vendored uv transitive package: after uv remove of its parent, scan --prune, vendor --revert, remove and rollback drift-keep it, so vendor --check stays red and its prune remedy loops (project and script lanes) #1287
Description
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
When the vendored package is transitive, vendored uv mode wires it through [tool.uv] override-dependencies = ["six==1.16.0"] plus [tool.uv.sources] six = { path = ".socket/vendor/pypi/<uuid>/…whl" }, and uv.lock's [manifest] constraints / overrides. If the user later runs uv remove python-dateutil (the parent that pulled six in), uv drops six's [[package]] entry, but it never touches [tool.uv], so the socket-written override and source (and the manifest lines) stay. The "removed, not drift" rule from #1147 (projects) and #1231 (scripts) fires only when no wired file contains the uuid any more. Here the leftover sources/override still contain it, so the vanished [[package]] fragment is reported as vendor_lock_entry_drifted and the whole revert is kept.
The result is the same stuck loop #1140 / #1214 fixed for direct packages: vendor --check exits 1 with "dependency removed: no lockfile resolves pkg:pypi/six@1.16.0 any more … run socket-patch scan --mode vendored --prune", that prune exits 0 and changes nothing, and check stays red.
Impact
A CI gate on vendor --check stays red for good after a routine uv remove of a direct dependency. Every remedy the tool names either exits 0 without doing anything or exits 1. The dead wheel, the ledger entry and the socket-written override-dependencies / sources lines stay committed, and the override goes on pinning six==1.16.0 to the vendored wheel if anything pulls six back in later. Nothing installs unpatched.
Repro (Linux, main e9be746, uv 0.5.31 and 0.12.24)
Patch data came from a local mock of the patch API (six@1.16.0), the same one used for earlier uv issues.
git init -q app && cd app
cat > pyproject.toml <<'EOF'
[project]
name = "demo"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = ["python-dateutil==2.8.2", "attrs>=20"]
[tool.uv]
constraint-dependencies = ["six==1.16.0"]
EOF
uv lock -q && uv sync -q && git add -A && git commit -qm init
socket-patch scan --mode vendored --yes --json # exit 0; adds override-dependencies + sources.six, rewrites uv.lock
uv remove python-dateutil # six gone from [[package]]; [tool.uv] lines and [manifest] stay
socket-patch vendor --check; echo $? # 1: "dependency removed … run `socket-patch scan --mode vendored --prune`"
socket-patch scan --prune --mode vendored --yes; echo $? # 0, nothing changes
socket-patch vendor --check; echo $? # still 1
socket-patch vendor --revert --json; echo $? # 0 success, removed 0:
# vendor_lock_entry_drifted "uv.lock fragment for Some(\"six\") changed since vendoring; left untouched"
# + vendor_artifact_kept + vendor_revert_kept
socket-patch remove pkg:pypi/six@1.16.0 --yes; echo $? # 1
socket-patch rollback --yes; echo $? # 1The PEP 723 script lane behaves the same way: a script with dependencies = ["python-dateutil==2.8.2", "attrs>=20"] and # [tool.uv] / # constraint-dependencies = ["six==1.16.0"], run through uv lock --script s.py, vendored, then uv remove --script s.py python-dateutil.
Expected vs actual
- Expected: what Fix vendored revert reading a removed dependency as drift (#1132, #1140, #1142) #1147 / Fix uv script lock revert stuck as drift (#1214) #1231 do for a direct package. The six
[[package]]record has nothing left to restore (vendor_lock_entry_removed), the pyproject / script[tool.uv]records that socket-patch wrote (override-dependencies,sources.six) and the uv.lock[manifest]records are unwound to the recorded originals, the wheel and ledger entry are deleted, andvendor --checkturns green. CLI_CONTRACT.md hasscan --prunerevert vendored entries whose dependency is gone, and that's the remedyvendor --checknames. - Actual: the surviving socket-written lines keep the uuid "referenced", so the missing package record counts as drift and every unwind keeps everything.
Matrix (Linux, real uv lock / uv remove, fresh fixture per cell)
| uv | lane | scan --prune |
vendor --revert |
remove |
rollback |
vendor --check after |
|---|---|---|---|---|---|---|
| 0.5.31 | project (uv remove python-dateutil) |
0, kept | 0 drift, kept | 1 | 1 | 1 |
| 0.5.31 | script (uv remove --script s.py python-dateutil) |
0, kept | 0 drift, kept | 1 | 1 | 1 |
| 0.12.24 | project | 0, kept | 0 drift, kept | 1 | 1 | 1 |
| 0.12.24 | script | 0, kept | 0 drift, kept | 1 | 1 | 1 |
| 0.12.24 | control: direct six, uv remove --script s.py six (#1214) |
0, reverted | 0 | 0 | 0 | 0 |
macOS / Windows not probed: this is uuid text matching with no OS-specific branch.
Suspect code
crates/socket-patch-core/src/vendor/pypi_uv.rs:832-852(revert_uv):unreferencedis true only when neither pyproject.toml nor uv.lock contains the uuid. For a transitive package, the vendor-written[tool.uv.sources]/override-dependenciesand the[manifest]lines surviveuv remove <parent>, soremoved()never fires for the missing[[package]]record.crates/socket-patch-core/src/vendor/pypi_lock.rs:737-754(revert_python_locks): the same global check for script locks.
A narrower rule may work: a record is "removed" when its own fragment (the [[package]] entry for the key) is gone and the only surviving uuid references are fragments this ledger entry itself wrote and can restore. Those surviving records would then be reverted normally.
Related: #1140 / #1214 (direct-package removal, fixed), #1285 (two scripts, one drops six).
Activity
uv: run 36 ledger (#1285, #1287)
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). A normal uv remove of a direct dependency must not strand its vendored transitive dependency and leave vendor --check permanently red. Give the ordinary project lane a cleanup path that actually works.
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: uv revert treats the vanished transitive [[package]] fragment as drift while socket-written override/source records still reference the uuid). Branch: agent/v5-uv-transitive-drift. Claim-ID: 20261009T164156Z-ddbaa5
Unwind vendored uv transitive deps after uv remove
mikolalysenko commentedon Oct 9, 2026
[agent] uv bug-hunt run 37 (ledger #310): this still reproduces on main
85105c9. I checked PR #1337 (90c618a) on Linux with uv 0.5.31 and 0.12.24. The parent was a main dependency,--dev, or--group lint. Afteruv remove <parent>I ran each ofvendor --revert,remove six,rollbackandscan --mode vendored --prune. That's 24 cells, and all of them converge on the PR: exit 0, check 0, wheel gone, no uuid left,uv sync --lockedok. The user's ownconstraint-dependencies = ["six==1.16.0"]and[manifest] constraintsspecifier come back intact. The PEP 723 script lane also converges.One neighbour is not covered by the PR. If the user re-adds
six==1.16.0as a direct dependency, the revert half-reverts the pair. That happens after removing the parent and also with the parent kept. I filed it separately as #1374.Generated by Claude Code