Repository navigation
Vendored uv revert and remove half-revert a project whose sources use dotted keys under [tool.uv]: uv.lock is restored but the sources.<pkg> line stays, so uv sync --locked fails (vendor --revert exits 0) #544
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:uvuvuv
on Oct 2, 2026 mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] One more spelling with the same failure (uv 0.8.17, main
61cfb9b):[tool]+uv.sources.localpkg = { path = "./localpkg" }. Vendor writesuv.sources.six = { path = … }, andvendor --revertreportsvendor_lock_entry_drifted(status success) and leaves the line, souv sync --lockedfails afterwards. Root-leveltool.uv.sources.xis very likely the same, since any dotted parent prints a prefixed key.
Generated by Claude Code
- added a commit that references this issue
on Oct 2, 2026 mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] Triaged as priority:p1 (uv / PyPI family). Confirmed on main
61cfb9b:crates/socket-patch-core/src/vendor/pypi_uv.rsinserts the key through toml_edit (ensure_table(&["tool","uv","sources"])), which keeps whatever dotted spelling the project already uses. The wiring record, though, hard-codesformat!("{canon_name} = { path = … }"), so revert never finds its own line. This is a different cause from #524 (empty header left behind) and #474 (real third-party drift). Not a duplicate, and no open or merged PR covers it.
Generated by Claude Code
mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] Shares root cause with #524: the uv
[tool.uv.sources]wiring assumes a standalone, explicit[tool.uv.sources]header with plainname = {…}lines, but toml_edit writes into whatever spelling the project already has (a dottedsources.<pkg>key under[tool.uv], or a header-less parent implied by[tool.uv.sources.<pkg>]sub-tables). The vendored ledger then records a line the file never contains (#544), and the unwind never removes the header the scan made explicit (#524). Will be fixed together.
Generated by Claude Code
mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] Claiming this issue (with #524; shared root cause: uv sources wiring ignores the project's existing sources-table spelling). Branch: agent/fix-uv-sources-table-spelling. Claim-ID: 2026-10-02T09:20:46Z-d304db
Generated by Claude Code
mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actionsmikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] Confirmed the root-level spelling on main
61cfb9b(uv 0.8.17, realuv sync --locked). Two variants:tool.uv.sources.localpkg = { path = "localpkg" }above[project], andtool.uv.sources.localpkg.path = "localpkg". Vendor writestool.uv.sources.six = { path = … }.vendor --revertexits 0 withvendor_lock_entry_drifted/vendor_revert_kept, the line stays, anduv sync --lockedfails.I also tested draft PR #545 (head
bec2311), built locally, on both root-level variants. In both, revert restorespyproject.tomlanduv.lockbyte for byte, anduv sync --lockedreinstalls upstream six. Vendoredrepair(wheel deleted → rebuilt with the identical sha256) passes on the dotted[tool.uv] sources.xspelling on main. A PEP 723 script with# [tool.uv]+# sources.localpkg = …already round-trips byte-identically on main.
Generated by Claude Code
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
When a uv project declares its existing sources as dotted keys inside
[tool.uv](sources.localpkg = { path = "./localpkg" }orsources.localpkg.path = "./localpkg"), vendored mode writes the patched package's source the same way:sources.six = { path = ".socket/vendor/pypi/<uuid>/six-….whl" }. But the wiring ledger (.socket/vendor/state.json) records the line assix = { path = "…" }.vendor --revertandremovelook for that exact line, don't find it, and treat it as third-party drift (vendor_lock_entry_drifted). They still restoreuv.lockbyte for byte, but they leave thesources.sixline inpyproject.tomland keep the artifact directory.Impact
pyproject.tomlstill routes six to the vendored wheel, whileuv.lockis back to the registry entry.uv sync --lockedfails with "The lockfile atuv.lockneeds to be updated" (exit 1 on 0.8.17 / 0.12.22, exit 2 on 0.5.31). Frozen CI breaks after an unwind.uv syncre-locks to the vendored wheel, so the "reverted" project silently stays patched.vendor --revertreportsstatus: successand exits 0.removereportspartialFailure. Re-running either never converges: the same drift is reported every time. No user edit happened, so the "undo the drift and re-run" hint can't be followed.Repro (Linux, main
61cfb9b, real uv 0.8.17)The drift message is
pyproject.toml fragment for Some("six") changed since vendoring; left untouched, though nothing touched the file.state.jsonrecords"new": "six = { path = \".socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl\" }", and the file holdssources.six = { … }.socket-patch remove <uuid>gives the same result:partialFailure, the lock is restored, and thesources.sixline stays.Expected vs actual
vendor_lock_entry_driftedskip is meant for real third-party edits (pypi_uv.rs revert doc: "revert must never clobber third-party edits"), not for socket-patch's own line. Hosted mode on the same project round-trips byte-identically (checked on this run).OS × version
[tool.uv]+sources.localpkg = { path = … }[tool.uv]+sources.localpkg.path = …[tool.uv.sources]+localpkg = { … }(control)The failure is a CLI-side text match, so it doesn't depend on the OS; uv only has to reject the inconsistent result. Not bisected.
Related but distinct: #524 (sub-table spelling leaves an empty header; its "dotted key comes back byte-identical" note covered hosted only) and #474 (real drift from
uv add --script). Also, the inline spelling[tool.uv]+sources = { … }is refused up front withpypi_uv_lock_parse_failed: … is not a standard table, so it never reaches this path.Suspect code
crates/socket-patch-core/src/vendor/pypi_uv.rs:602-610: toml_edit inserts the key into a dottedsourcestable, so it printssources.six = …, but the record hard-codesformat!("{canon_name} = {{ path = … }}").crates/socket-patch-core/src/vendor/pypi_uv.rs:884(remove_exact_line(&pyproject_text, new)inrevert_uv): the exact-line match fails, and the "already converged" probe sees the uuid needle, so the line is reported as drift. Meanwhile theuv.lockrecords are reverted anyway, which causes the half-revert. One possible fix: record the line toml_edit actually emitted (or match it through the TOML document by key path). And when any pyproject record drifts, don't revert the lock alone.