Skip to content

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 $?                     # 1

The 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, and vendor --check turns green. CLI_CONTRACT.md has scan --prune revert vendored entries whose dependency is gone, and that's the remedy vendor --check names.
  • 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): unreferenced is true only when neither pyproject.toml nor uv.lock contains the uuid. For a transitive package, the vendor-written [tool.uv.sources] / override-dependencies and the [manifest] lines survive uv remove <parent>, so removed() 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

  1. added
    v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.
    uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.
    compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.
    on Oct 9, 2026
  2. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    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.

  3. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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

  4. added a commit that references this issue on Oct 9, 2026
    90c618a
  5. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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. After uv remove <parent> I ran each of vendor --revert, remove six, rollback and scan --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 --locked ok. The user's own constraint-dependencies = ["six==1.16.0"] and [manifest] constraints specifier 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.0 as 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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claimedagent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentcompatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.pm:uvuvpriority:p1uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions