Skip to content

Fix stale specifier on uv script-lock revert (#869) - #1458

Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-uv-script-lock-revert-specifier
Open

Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-uv-script-lock-revert-specifier

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #869

Summary

Reverting a vendored PEP 723 script lock (vendor --revert, remove,
rollback) now restores the [manifest] specifier the script declares
at revert time, not the one recorded when vendoring. Before, a user who
changed the vendored package's requirement while it was vendored
(uv add --script s.py "six>=1.15") got the stale ==1.16.0 written back
with status: success, and every later uv run --locked --script failed.

Root cause

A path source has no specifier, so uv leaves s.py.lock byte-identical when
the script's requirement changes. revert_python_locks
(vendor/pypi_lock.rs) then hit restore_document's live == new path and
returned the recorded original verbatim. #841 fixed the same shape for
project uv.lock files (respell_original in vendor/pypi_uv.rs), but
script locks go through this separate backend.

Fix

  • New respell_script_lock: before the document restore, each [manifest]
    entry (requirements / constraints / build-constraints / overrides) for the
    vendored package gets its specifier re-derived from the live PEP 723 block
    through the same respell_lock_specifier rule the uv.lock lane uses,
    so both lanes agree on spelling, markers and ambiguity.
  • A multi-clause range (uv's spelling not derivable) is treated as drift:
    vendor_lock_entry_drifted, both files kept, as the project lane does.
  • If the script's declarations are unchanged since vendoring, the recorded
    lock is kept untouched (byte-exact), so an ambiguous declaration match
    can't turn an untouched script into drift (found by /code-review).
  • respell_lock_specifier now takes the metadata file's name, so its
    messages name the script instead of pyproject.toml / uv.lock.
  • Docs: docs/testing/uv-compatibility.md notes that script locks follow the
    same rule.

No wrapper (npm/pypi/gem) changes: this is core-only behaviour.

Test evidence

Per-issue checklist:

Red → green:

  • Unit tests on origin/main: both new revert tests fail (stale ==1
    restored, no warning); pass with the fix.
  • E2E with uv 0.11.32, core change reverted: fails with
    { name = "six", specifier = "==1.16.0" } in the restored lock; passes
    with the fix.
  • I also checked respell_script_lock against locks real uv wrote for
    six>=1.15, six==1.16.* and bare six. Its output was byte-identical
    to uv's in all three, which caught two padding bugs.

Commands run locally:

  • cargo fmt --all -- --check: ok
  • cargo clippy --workspace --all-features -- -D warnings: ok
  • cargo test -p socket-patch-cli --all-features --test e2e_vendor_pypi_build: 47 passed, 7 ignored
  • cargo test --workspace --all-features --no-fail-fast: everything passes except
    13 tests in untouched modules that fail only in this sandbox. They inject
    write failures with chmod, which root ignores (covgap_commands_vendor,
    in_process_redirect, repair, copy_tree, vlt_heal, pypi_poetry,
    pypi_requirements). One more, e2e_bun_lockb, needs patches-api.socket.dev,
    which the sandbox can't reach. CI runs them unprivileged and online.

/code-review high findings not fixed

🤖 Generated with Claude Code

https://claude.ai/code/session_01XPNBoBdAu7JJXCUQipjXY8


Note

Medium Risk
Changes vendored revert behavior for PEP 723 script locks and shared lock-specifier logic; incorrect respelling could break uv lock --script --check, but drift paths fail closed and tests cover the scenario.

Overview
Fixes #869: when you change a vendored package's requirement in a PEP 723 script (uv add --script s.py "six>=1.15") while it stays vendored, uv leaves s.py.lock unchanged because path sources record no specifier. vendor --revert used to write the old recorded specifier back with success, breaking uv run --locked --script.

The fix mirrors #840 for project uv.lock: before restoring a script lock, respell_script_lock re-derives each [manifest] entry (requirements, constraints, build-constraints, overrides) for the unwound package from the live # /// script block via shared respell_lock_specifier. Unchanged declarations since vendoring still revert byte-for-byte; multi-clause ranges that uv's lock spelling can't be derived from are treated as vendor_lock_entry_drifted (files kept). respell_lock_specifier now takes a metadata rel so errors name the script file, not only pyproject.toml.

Adds unit tests in pypi_lock.rs, e2e uv_vendor_script_revert_after_declaration_edit, and a note in docs/testing/uv-compatibility.md.

Reviewed by Cursor Bugbot for commit 8865851. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
Reverting a vendored PEP 723 script lock (vendor --revert, remove,
rollback) wrote back the specifier recorded at vendor time. If the
user had changed the package's requirement in the script while it
was vendored, uv left the lock untouched, so the revert put the
stale specifier back, reported success, and every later
`uv run --locked --script` failed.

The revert now re-derives the [manifest] specifier from the live
script block, using the same rule the uv.lock lane uses for
pyproject.toml. A spelling uv's output can't be predicted for
(a multi-clause range) is kept as drift with both files untouched.

Fixes #869

Assisted-by: Claude Code:claude-opus-5-5
End-to-end check with real uv: vendor a PEP 723 script, change the
vendored package's requirement with `uv add --script`, revert, and
require `uv lock --script --check` to accept the restored pair.

Refs #869

Assisted-by: Claude Code:claude-opus-5-5
A script whose declarations are the same as right after vendoring
now keeps the recorded lock without re-deriving anything, so an
ambiguous declaration match can't turn an untouched script into
drift. Warnings for an underivable spelling now name the script
instead of pyproject.toml and uv.lock.

Refs #869

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 13, 2026 01:31
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

1 similar comment
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


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 8865851. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Ready for review at 88658518807207bb439d38150a3d576000aff3ce.

  • CI: all green on this head (36 pass + 50 skipped), no failures.
  • Bugbot: reviewed 8865851, no new issues; no unresolved review threads.
  • Mergeable, no CHANGELOG.md changes.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: Reverting a vendored package in a PEP 723 script lock (s.py.lock) used to restore the [manifest] specifier recorded at vendor time, even when the user had since edited the script's dependencies. That left a lock uv lock --script --check rejects. The new respell_script_lock re-derives the package's manifest entries (requirements, constraints, build-constraints, overrides) from the live script block before the restore, using the same rule the pyproject lane already uses (#840). Fixes #869.

Risk: low. When the script's declarations are unchanged, the recorded lock is returned byte-for-byte. Every new failure path falls back to the existing vendor_lock_entry_drifted handling and writes nothing. A missing or broken script block keeps the old behaviour.

Look here:

  • pypi_lock.rs:967: respell_script_lock. Line 977 is the unchanged-declarations early exit, and lines ~1029-1052 insert or remove the specifier.
  • pypi_lock.rs:1274: the hook in revert_python_locks. An Err becomes drift plus continue.
  • uv.rs:1295:`` respell_lock_specifier gains a `rel` argument for error text.
  • e2e_vendor_pypi_build.rs:1311:``` real-uv e2e, edit then revert then uv lock --script --check.

Verified:

  • Read the full diff and the surrounding revert and respell code.
  • Line endings: CRLF is kept (preserve_line_endings, fixture test).
  • Untouched content: the byte-exact path is tested.
  • Multiple scripts: each is keyed per lock/script pair.
  • Clause order: specifiers are compared order-insensitively.
  • The new unit tests and the e2e lock assertion would fail without the fix.
  • CI on 8865851: 36 success, 50 skipped of 86; ci-ok and clippy green. Cursor Bugbot passed. No unresolved review threads. CHANGELOG.md untouched.

Changes I made: none.

Open questions (non-blocking):

  • The early exit compares all of the script's declarations, not just the vendored package's. So an unrelated script edit plus an ambiguous or-marker declaration of the vendored package now reports drift where the revert used to succeed. It fails closed, but it's rare. A follow-up could compare only the declarations for the vendored name.
  • In uv.rs, the parameter pyproject_text now also receives PEP 723 metadata, and the LockRequirementArray::Manifest doc lists only constraints and build-constraints. Both are naming-only nits.

Auto-merge (squash) is armed, so approving sends it straight to the merge queue.


Generated by Claude Code

This branch has not been deployed

No deployments
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

2 participants