You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Vendored uv script lock: after the user changes the vendored package's specifier in the PEP 723 block, vendor --revert / remove / rollback write the stale ==1.16.0 back into <script>.py.lock, so uv run --locked fails (exit 0) #869
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
This is #840's shape in the PEP 723 script-lock lane, which #841 didn't cover. #841 re-derives the restored specifier only for uv.lock's requires-dist, requires-dev and [manifest] constraints (respell_original, crates/socket-patch-core/src/vendor/pypi_uv.rs:1064). Script locks are reverted by a different backend (revert_python_locks, crates/socket-patch-core/src/vendor/pypi_lock.rs:742), which still restores the recorded original.
When six is vendored for s.py, the .py.lock[manifest] requirements element becomes { name = "six", path = ".socket/vendor/…" }, without a specifier. If the user then changes six's requirement in the script (uv add --script s.py "six>=1.15", or a hand edit plus uv lock --script s.py), uv leaves s.py.lockbyte-identical. On revert, the lock record therefore still equals its recorded new, and restore_document (pypi_lock.rs:702) returns the recorded original verbatim, with specifier = "==1.16.0". The script record is merged (pypi_lock.rs:776-784): the tool.uv.sources line is dropped and the user's six>=1.15 is kept. Both records restore "cleanly", so no drift is reported, and the pair ends up inconsistent.
Impact
vendor --revert, remove and rollback exit 0 with status: "success" and no warning. After that, uv run --locked --script s.py and uv lock --script s.py --locked fail with "The lockfile at uv.lock needs to be updated, but --locked was provided". A plain uv run --script silently relocks. This is the same CI breakage #840 described for projects.
Repro
This uses the bug-hunt mock patch API (serves POST /patch/package and a patched six 1.16.0 wheel with SRI sha512), the same harness as #806, #821 and #840.
mkdir sc &&cd sc
cat > s.py <<'EOF'# /// script# requires-python = ">=3.9"# dependencies = [# "six==1.16.0",# "attrs>=20",# ]# ///import sixprint(getattr(six, "SOCKET_PATCHED", 0))EOF
uv lock --script s.py
SP="socket-patch --api-url $MOCK --api-token fake --org acme --patch-server-url $MOCK --vendor-url $MOCK"$SP scan --mode vendored --yes --json # s.py.lock: { name = "six", path = ".socket/vendor/…" }
cp s.py.lock vend.lock
uv add --script s.py "six>=1.15"# s.py now says six>=1.15
cmp vend.lock s.py.lock &&echo identical # identical
uv run --locked --script s.py # 1 (patched), lock ok$SP vendor --revert --yes --json # exit 0, "status": "success", no events
grep 'name = "six", specifier' s.py.lock # { name = "six", specifier = "==1.16.0" } <- stale
uv run --locked --script s.py # error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided.
remove pkg:pypi/six@1.16.0 and rollback pkg:pypi/six@1.16.0 behave the same, and so do a hand edit plus uv lock --script s.py and other specifiers (six==1.16.*, a bare six, six>=1.16,<1.17).
Expected vs actual
Expected (docs/testing/uv-compatibility.md, "Revert state retains the original wiring"): "Script and lock edits are treated as a pair: conflicting changes preserve both files and their recovery state rather than restoring only one side." After Fix uv revert restoring a stale specifier (#840) #841, the same doc also says revert restores entries "with the specifier pyproject.toml declares at revert time, not the one recorded when vendoring". The script lane should do the same from the PEP 723 block, or, if it can't derive uv's spelling, treat the edit as drift and keep both files (vendor_lock_entry_drifted), as the project lane does for multi-clause ranges.
Actual: the stale recorded [manifest] requirements element is written back, exit 0, status: "success", and the lock no longer matches the script.
Control (pass): the vendored → hosted takeover (scan --mode hosted) on the same edited script re-derives the requirement, and uv lock --script --locked passes (all OS × versions below). The project-lane equivalent (#840) passes on main for deps, extras and dev groups × 11 specifier spellings × uv 0.2.37 / 0.4.30 / 0.5.31 / 0.8.17 / 0.12.23.
Matrix (main 99f61d2)
"fail" = 0 vendor refs left in s.py and s.py.lock, s.py.lock six element specifier = "==1.16.0", and uv lock --script s.py --locked non-zero. Each cell is {uv add --script, hand edit + uv lock --script} × {six>=1.15, six==1.16.*} × {vendor --revert, remove, rollback} = 12 unwinds.
OS
uv 0.5.31
uv 0.8.17
uv 0.12.23
V→H takeover
Linux (sandbox)
fail (12/12, plus >=1.16, bare six, multi-clause)
fail (12/12)
fail (12/12)
pass
Windows (windows-latest probe)
fail (12/12)
job ran, not read in detail
fail (12/12)
pass (4/4 per version)
macOS (macos-latest probe)
pending (runners queued)
pending
pending
–
Not bisected. The behaviour comes from the recorded-original design of the script-lock backend, and #841 fixed only the uv.lock lane.
Suspect code
crates/socket-patch-core/src/vendor/pypi_lock.rs:786-787: the .py.lock record goes through restore_document(&live, original, new), which returns original whenever live == new (pypi_lock.rs:702), with no re-derivation of the [manifest] requirements specifier from the current PEP 723 block.
crates/socket-patch-core/src/vendor/pypi_lock.rs:776-784: the script record merges the user's edit, so the two records disagree without either reporting drift.
Compare the project lane's fix: respell_original / respell_lock_specifier (pypi_uv.rs:1064, redirect/upstream/uv.rs).
[agent] Matrix update from the probe run (https://github.com/SocketDev/socket-patch/actions/runs/37326331477): macOS (macos-latest, arm64) × uv 0.12.23 also fails 12/12. Every vendor --revert / remove / rollback exits 0 with "status": "success", the s.py.lock six element is specifier = "==1.16.0", and uv lock --script s.py --locked exits 1. The V→H takeover passes 4/4. All 9 OS × version jobs completed; I read the logs for Windows 0.5.31 / 0.12.23 and macOS 0.12.23.
[agent] Triaged as priority:p1 (uv). Still present on main 0d302dc: revert_python_locks (vendor/pypi_lock.rs:742) restores the .py.lock record through restore_document (:697), which returns the recorded original whenever the live lock equals the recorded new. Nothing in that path re-derives the [manifest] requirements specifier the way respell_original does for uv.lock. This isn't a duplicate: #840 / #841 covered only the project lane. No open PR addresses it yet.
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
This is #840's shape in the PEP 723 script-lock lane, which #841 didn't cover. #841 re-derives the restored specifier only for uv.lock's
requires-dist,requires-devand[manifest] constraints(respell_original,crates/socket-patch-core/src/vendor/pypi_uv.rs:1064). Script locks are reverted by a different backend (revert_python_locks,crates/socket-patch-core/src/vendor/pypi_lock.rs:742), which still restores the recorded original.When six is vendored for
s.py, the.py.lock[manifest] requirementselement becomes{ name = "six", path = ".socket/vendor/…" }, without a specifier. If the user then changes six's requirement in the script (uv add --script s.py "six>=1.15", or a hand edit plusuv lock --script s.py), uv leavess.py.lockbyte-identical. On revert, the lock record therefore still equals its recordednew, andrestore_document(pypi_lock.rs:702) returns the recordedoriginalverbatim, withspecifier = "==1.16.0". The script record is merged (pypi_lock.rs:776-784): thetool.uv.sourcesline is dropped and the user'ssix>=1.15is kept. Both records restore "cleanly", so no drift is reported, and the pair ends up inconsistent.Impact
vendor --revert,removeandrollbackexit 0 withstatus: "success"and no warning. After that,uv run --locked --script s.pyanduv lock --script s.py --lockedfail with "The lockfile atuv.lockneeds to be updated, but--lockedwas provided". A plainuv run --scriptsilently relocks. This is the same CI breakage #840 described for projects.Repro
This uses the bug-hunt mock patch API (serves
POST /patch/packageand a patched six 1.16.0 wheel with SRI sha512), the same harness as #806, #821 and #840.remove pkg:pypi/six@1.16.0androllback pkg:pypi/six@1.16.0behave the same, and so do a hand edit plusuv lock --script s.pyand other specifiers (six==1.16.*, a baresix,six>=1.16,<1.17).Expected vs actual
vendor_lock_entry_drifted), as the project lane does for multi-clause ranges.[manifest] requirementselement is written back, exit 0,status: "success", and the lock no longer matches the script.Control (pass): the vendored → hosted takeover (
scan --mode hosted) on the same edited script re-derives the requirement, anduv lock --script --lockedpasses (all OS × versions below). The project-lane equivalent (#840) passes on main for deps, extras and dev groups × 11 specifier spellings × uv 0.2.37 / 0.4.30 / 0.5.31 / 0.8.17 / 0.12.23.Matrix (main
99f61d2)"fail" = 0 vendor refs left in
s.pyands.py.lock,s.py.locksix elementspecifier = "==1.16.0", anduv lock --script s.py --lockednon-zero. Each cell is {uv add --script, hand edit +uv lock --script} × {six>=1.15,six==1.16.*} × {vendor --revert,remove,rollback} = 12 unwinds.>=1.16, baresix, multi-clause)Not bisected. The behaviour comes from the recorded-original design of the script-lock backend, and #841 fixed only the uv.lock lane.
Suspect code
crates/socket-patch-core/src/vendor/pypi_lock.rs:786-787: the.py.lockrecord goes throughrestore_document(&live, original, new), which returnsoriginalwheneverlive == new(pypi_lock.rs:702), with no re-derivation of the[manifest] requirementsspecifier from the current PEP 723 block.crates/socket-patch-core/src/vendor/pypi_lock.rs:776-784: the script record merges the user's edit, so the two records disagree without either reporting drift.respell_original/respell_lock_specifier(pypi_uv.rs:1064,redirect/upstream/uv.rs).Probe run: https://github.com/SocketDev/socket-patch/actions/runs/37326331477 (
bughunt/uv/20261005-script-spec-revert).Related: #840 (project lane, fixed by #841), #474 (script revert after an unrelated
uv add --script, fixed).Backlog review — 2026-10-08
Priority: P1 → P2. Specifier drift in uv script locks causes an unwind problem in a specific edited layout; retain at P2.