Repository navigation
Fix uv script-lock revert per pair (#1285, #890) - #1391
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A vendored PyPI entry can wire several PEP 723 scripts (a.py and b.py, each with its .py.lock) or several PEP 751 locks at once. The revert judged the whole entry together, so a change to one script stranded all of them: - After `uv remove --script b.py six`, a.py still named the vendored wheel, so b.py's removal counted as drift. vendor --revert left a.py wired, and remove and rollback exited 1 (#1285). - Deleting b.py, b.py.lock or pylock.dev.toml failed every unwind with "cannot read ./b.py.lock", and the hosted takeover pointed at the same failing command (#890). The revert now decides per pair: a pair that no longer names the wheel is removed, not drift, and a wired file that no longer exists warns vendor_lockfile_missing and is skipped while the remaining files are restored. Other read errors still fail closed, and a hand edit inside a still-wired pair is still drift. Fixes #1285 Fixes #890 Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups to the per-pair revert: - Read every wired file once and judge every record against that text, so a file deleted mid-revert cannot fail it with "cannot read". - Warn vendor_lockfile_missing once per file, in wording that holds on a dry run or a drift-kept revert. - Pair a .py.lock with its script through script_of_lock. - The hosted takeover now passes the missing-file advisory through, so scan --mode hosted names a deleted script too. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The hosted takeover reverts inside a group commit, whose in-memory overlay can hold a removal the disk does not show yet. The missing check looked at the disk while the read went through the overlay, so a file the run had removed still failed the revert with "cannot read". The check now uses the overlay-aware read and treats its NotFound as missing. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review at
Generated by Claude Code |
Final review briefWhat it does. Risk: low–medium. The change is confined to one revert function, and any read error other than Look here
Verified. Read the full diff. Changes I made. None. Open questions (non-blocking).
Auto-merge is armed: approving sends it straight to the merge queue. Generated by Claude Code |
The revert now skips a wired script that no longer exists. When the user renamed it (`git mv job.py tool.py`) rather than deleting it, tool.py still installs from the vendored wheel through its own [tool.uv.sources] path. But the residual-reference guard never read root *.py files, so the revert deleted that wheel and broke `uv run tool.py`. The guard now also reads root *.py files. uv resolves a script's source paths from the script's own directory, so only a root script can still reach .socket/vendor/. A renamed script now keeps the wheel with vendor_revert_residual_reference, naming the file. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b8bd9e6. Configure here.
Final review brief (update for
|
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1285
Fixes #890
Summary
A vendored PyPI entry can wire several PEP 723 scripts (
a.py+a.py.lock,b.py+b.py.lock) or several PEP 751 locks(
pylock.toml,pylock.dev.toml) at once. Its revert now handles eachpair separately, so a change to one script no longer strands the others.
Every unwind benefits:
vendor --revert,scan --prune,remove,rollback, and the vendored → hosted takeover.Root cause
revert_python_locks(crates/socket-patch-core/src/vendor/pypi_lock.rs)decided liveness for the whole ledger entry at once:
uv remove --scriptdrops the package from one of two vendored scripts, every unwind keeps the other script wired (revert exits 0, remove/rollback exit 1, hosted takeover refuses) #1285: oneunreferencedflag was cleared as soon as any wiredfile still named the uuid. After
uv remove --script b.py six,a.pystill named it, so b.py's records were drift instead of removed. The
drift then kept every pair, including the untouched
a.py, andremove/rollbackexited 1.vendor --revert,remove,rollbackand the hosted takeover can never unwind the other scripts, and the takeover's suggested fix is the command that fails #890: the same probe, and the per-record read, returnedRevertOutcome::failedon any read error, includingNotFound. Onedeleted script,
.py.lockorpylock.dev.tomlfailed every unwindwith
cannot read ./b.py.lock, and the takeover told users to run thesame failing command.
Fix
is
None. Any other read error still fails closed.uv remove --scriptdrops a vendored package from a PEP 723 script lock,scan --prune,vendor --revert,remove,rollbackand the hosted takeover still drift-keep it, sovendor --checkstays red and its prune remedy loops #1214's rule) is decided per pair: a.py.lockgoes with its script (script_of_lock), and eachpylock*.tomlis its own pair. A hand edit inside a pair that stillroutes through the wheel is still drift.
vendor_lockfile_missingonce,naming the file (the gem backend's precedent), and the files that still
exist are restored. The pypi dispatcher's residual-reference guard
still keeps the wheel if any other project file names it.
vendor_lockfile_missingthrough, next tovlt's reinstall notice, so
scan --mode hostednames the deleted filetoo.
CLI_CONTRACT.mddocuments the per-pair rule and the missing-fileadvisory.
Out of scope: #890 has a later comment saying a deleted
uv.lockblocksthe project lane. That goes through a different backend
(
pypi.rsrevert_uv) and is a separate root cause, so it is not claimedhere.
Tests (red → green)
Per issue:
uv remove --scriptdrops the package from one of two vendored scripts, every unwind keeps the other script wired (revert exits 0, remove/rollback exit 1, hosted takeover refuses) #1285:pypi_lock::tests::uv_remove_from_one_of_two_scripts_reverts_the_other(core), plus the
uv remove brow ofmode_migration_pypi::multi_script_unwinds_after_one_script_changes(CLI:vendor --revert,remove,rollback, hosted takeover). Before thefix it fails with
vendor_lock_entry_drifted(b.py.lock changed since vendoring).vendor --revert,remove,rollbackand the hosted takeover can never unwind the other scripts, and the takeover's suggested fix is the command that fails #890:deleted_script_or_lock_does_not_block_the_other_scripts(deleted both, only
.py.lock, or only.py; dry and wet) anddeleted_pylock_does_not_block_the_other_pylock(core), plus the fourrm …rows of the CLI test across the same unwinds. Before the fixthey fail with
cannot read …/b.py.lock: No such file or directory.drift_in_a_still_wired_pair_keeps_the_entryandunreadable_wired_file_still_fails_closed(a directory where the lockwas still fails closed).
b.pyis deleted: hosted mode refuses the orphanb.py.lockon its own (redirect_uv_script_missing) and keeps theproject vendored and untouched. The CLI test pins that this is the
hosted refusal and not a revert failure.
Local runs:
cargo fmt --all -- --checkandcargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features --no-fail-fast: everything thisPR touches passes. 13 tests fail in this sandbox and also fail on
mainhere. 12 are permission-injection tests (*_write_failure_*,*unremovable*,relax_loop_must_not_traverse_symlinked_root) thatcan't fail a write when run as root. The 13th,
e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed,fails on an npm
missing_blobdownload; I re-ran it onmainwith thesame result.
cargo test -p socket-patch-cli --all-features --test e2e_vendor_pypi_build -- --include-ignored(uv 0.11.32): 52 passed.npm/,pypi/,gem/): the revert logic is only in the Rust core./code-review highran on the diff. Its findings are fixed in 194f0c3:one read per file (closes a delete-mid-revert race), a deduplicated
warning in wording that is also true on a dry or drift-kept run,
script_of_lockreused, and the takeover forwarding the advisory.Bugbot (194f0c3) found that the missing-file probe read the disk, not the
GroupCommitoverlay the takeover reverts inside. Fixed in e7fae29, withthe regression test
missing_is_judged_through_the_group_commit_overlay(red before the fix, green after).
Human review (e7fae29) found that a renamed vendored script (
git mv a.py tool.py) would lose its wheel, because the residual-reference guard never readroot
*.pyfiles. Fixed in b8bd9e6, with the testmode_migration_pypi::renamed_vendored_script_keeps_its_wheel(red beforethe fix, green after).
🤖 Generated with Claude Code
https://claude.ai/code/session_01LSV2D8AnkmcqFDijhzZGhE
Generated by Claude Code