Skip to content

Fix uv script lock revert stuck as drift (#1214) - #1231

Merged
Mikola Lysenko (mikolalysenko) merged 2 commits into
mainfrom
agent/fix-uv-script-lock-removed-entry
Oct 9, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 2 commits into
mainfrom
agent/fix-uv-script-lock-removed-entry

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1214

Summary

After uv remove --script job.py six drops a vendored package from a PEP 723 script and its .py.lock, every vendored unwind (vendor --revert, scan --prune, remove, rollback) kept the entry as "drift". The wheel and ledger entry stayed forever, vendor --check stayed red, and the scan --prune remedy it names was a no-op that exited 0. Now the revert sees that nothing references the vendored wheel any more, retires the entry, and vendor --check turns green.

Root cause

revert_python_locks (crates/socket-patch-core/src/vendor/pypi_lock.rs) reverts the python_script_metadata and python_lock_document records. After the removal the lock no longer matches what was recorded, so the structural restore reported vendor_lock_entry_drifted. #1147 added a "nothing names the uuid → vendor_lock_entry_removed" arm for uv projects (revert_uv) only.

Fix

  • revert_python_locks probes every wired file once, before reverting anything. When none of them names the entry's uuid, each record whose written text carried it warns vendor_lock_entry_removed and is skipped, which matches revert_uv (Fix vendored revert reading a removed dependency as drift (#1132, #1140, #1142) #1147). The pypi dispatcher then deletes the wheel and ledger entry, and its residual-reference probe still keeps them if some other file installs from the wheel.
  • The write gate now blocks only on drift warnings (it used to block on any warning), so a removed record doesn't block the rest of the restore. This matches revert_uv's pair gate.
  • If any wired file still names the uuid, a hand edit stays drift and the entry is kept, as before.
  • Behaviour change: this applies to any edit that drops every reference to the uuid, not only uv remove --script. For example, a regenerated pylock.toml that re-pins the package from PyPI also retires the vendored entry rather than reporting drift. revert_uv already behaves this way for uv projects (Fix vendored revert reading a removed dependency as drift (#1132, #1140, #1142) #1147).
  • mode_migration_pypi.rs ~1437-1448 is a rustfmt-only reformat of the unrelated ledger_update_failure_changes_nothing test, with no behaviour change.

Hosted lane

scan --mode hosted never reverts vendored entries, for any ecosystem: it warns vendor_ledger_entry_unwired and names scan --prune as the fix. Before this change, following that remedy looped. Now it retires the entry, and the test asserts that end to end.

Tests (red → green)

Issue Test Without fix With fix
#1214 (mechanism) pypi_lock::tests::script_revert_after_uv_remove_is_removed_not_drift FAIL: vendor_lock_entry_drifted "job.py.lock changed since vendoring" pass
#1214 (guard) pypi_lock::tests::script_revert_keeps_drift_while_any_wired_file_names_the_uuid pass pass
#1214 (CLI: vendor --revert, scan --prune, remove, rollback, hosted scan → its prune remedy) mode_migration_pypi::script_lock_unwinds_after_uv_remove_script FAIL: vendor_lock_entry_drifted + vendor_artifact_kept + vendor_revert_kept pass

The CLI fixture for the post-removal script and lock is byte-identical to what real uv 0.11.32 remove --script writes. I checked this locally.

Local verification (head eec1e33)

  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo fmt --all -- --check: the two changed files are clean. main (f3c6313) already has rustfmt drift in 16 unrelated files, which I did not touch. CI has no fmt job.
  • cargo test -p socket-patch-core --all-features: 5889 passed. 4 failed, all permission tests (copy_tree, vlt_heal, pypi_poetry, pypi_requirements write-failure tests) that cannot fail a write when run as root in this sandbox. They are unrelated to this change.
  • socket-patch-cli lib/bins, plus every PyPI/uv/vendor/rollback/remove suite with --include-ignored (mode_migration_pypi, e2e_vendor_pypi_build, in_process_rollback_vendored, in_process_vendor_pypi_takeover, in_process_remove_repair_lifecycle, in_process_rollback_all_ecosystems, hosted_superseding_pypi, in_process_python_envs, covgap_commands_rollback, cli_remove_silent, e2e_pypi_multi_copy, in_process_pypi_apply, in_process_redirect_{pipenv,poetry,pdm}): all pass except 10 tests that fail the same way with this fix reverted (environmental: e2e_pypi needs the real API, 4 e2e_redirect_uv_build hosted pylock lanes on the sandbox's uv 0.11.32, pipenv_hosted_to_vendored_names_the_unpatched_requirements).
  • The full cargo test --workspace build exceeded the sandbox's disk allowance, so CI is the full-workspace gate.
  • No wrapper changes are needed. This is core revert logic only, and the npm/pypi/gem wrappers just dispatch to the binary.

Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
After `uv remove --script job.py six`, neither the PEP 723 script nor
its lock names the vendored wheel any more, but vendor --revert,
scan --prune, remove and rollback all kept the entry as "drift". The
wheel and ledger entry stayed forever and vendor --check stayed red,
with every remedy it named looping.

The script/pylock revert now probes the wired files once before
restoring: when none of them names the entry's uuid, each record that
routed through the wheel warns vendor_lock_entry_removed and the
revert finishes, as #1147 already does for uv projects. Real
third-party edits while any file still names the wheel stay drift.

Fixes #1214

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) force-pushed the agent/fix-uv-script-lock-removed-entry branch from 95fcbe4 to eec1e33 Compare October 9, 2026 06:36
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 9, 2026 06:55
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 eec1e33. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 9, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at eec1e33e0.

  • CI: every check suite on the head is green (no failures, no main-wide failures).
  • Bugbot: reviewed eec1e33e0, no findings; no open review threads.
  • Mergeable against main (e03a666d), no CHANGELOG changes.
  • Slack announcement: not sent this run (Slack send tool unavailable); next run retries.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: Reverting a PEP 723 script lock or pylock.toml entry that the user has since removed (for example, uv remove six) used to get stuck reporting "drift". revert_python_locks now reads every wired file first. If none of them still names the entry's uuid, it emits vendor_lock_entry_removed, and the pypi dispatcher then retires the wheel and the ledger entry. This is the same arm #1147 added to revert_uv.

Risk: low. The new arm fires only when no wired file names the uuid. The dispatcher's residual-reference probe still keeps the wheel if anything else in the project names it. Python-lock records are whole-file and their new text always carries the wheel path. So when the arm fires, every record is skipped, and narrowing the write gate to drift-only can't cause a partial write.

Look here:

Verified:

  • cargo test -p socket-patch-core --lib pypi_lock::tests: 25/25 pass, including the two new tests. Traced: without the arm, the live text matches neither new nor original and reports drift, so those tests would fail.
  • The other local failures are root-only write-failure tests, the environmental ones the description lists.
  • CI 481/481 green on eec1e33 (ci-ok, clippy). Bugbot passed. No CHANGELOG.md change, no unresolved threads.

Changes I made: none.

Open questions (non-blocking):

  • This is a policy change: any edit that drops the uuid now retires the entry, not only uv remove. One example is a regenerated pylock.toml that re-pins the package from PyPI. That matches revert_uv's behaviour, but the description could say it.
  • mode_migration_pypi.rs ~1437-1448 is a rustfmt-only reformat of an unrelated test (ledger_update_failure_changes_nothing).
  • Pre-existing, not in scope: if the wired file itself is deleted, the read still returns NotFound and the revert fails.

Auto-merge is armed: approving sends this straight to the merge queue.


Generated by Claude Code

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

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants