Repository navigation
Fix uv revert after a vendored dep goes direct (#1374) - #1384
Mikola Lysenko (mikolalysenko) merged 3 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
When a package vendored as a transitive uv dependency was later added directly (`uv add six==1.16.0`), uv wrote a root requires-dist element that routes through the vendored wheel. `vendor --revert`, `remove` and `rollback` never restored it, so they wrote a pyproject.toml and uv.lock that `uv sync --locked` rejects, and revert still exited 0. The revert now puts any such root requires-dist or requires-dev element back on the registry, spelled the way pyproject.toml declares it now. If it can't (no declaration, or a spelling it can't derive), or uv.lock still routes through the wheel after pyproject.toml stops naming it, it leaves both files untouched instead of writing a pair uv rejects. `vendor --check` no longer claims no file references a vendored PyPI artifact when one still does: it names the file and suggests re-locking first. Fixes #1374 Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the uv revert fix: - A promoted element whose declaration uv's spelling can't be derived from (`===`, a URL, an unreadable spec) now keeps both files instead of being restored with no specifier, which `uv sync --locked` would also reject. - The keep-both warnings tell the user what to change; re-running `uv lock` alone would rebuild the same element. - `vendor --check` reports the residual-reference probe's own clause, so an unreadable file is reported as such rather than as "no file references it". - Cover the `vendor --check` verdict with a CLI test. 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 b022bef. Configure here.
|
Ready for review at
Slack announcement pending (no Slack send tool in this run; the next run retries). Generated by Claude Code |
Final review briefWhat it does. When a vendored uv dep was later promoted to a direct dependency ( Risk: low. New code runs only when there's no drift and only touches root-unit elements named for the package whose 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 |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1374
Summary
In a uv project, vendor a transitive package and then add it directly (
uv add six==1.16.0). After that,vendor --revert,removeandrollbackwrote apyproject.toml/uv.lockpair thatuv sync --lockedrejects. The revert still exited 0, andvendor --checkthen wrongly said no file references the vendored wheel. Now:uv addcreated back on the registry, spelled the waypyproject.tomldeclares it now (the Vendored uv revert writes the pre-vendor specifier back into uv.lock after the user changes the vendored package's version spec, souv sync --lockedfails (vendor --revert / remove / rollback exit 0) #840 respell rule). This coversrequires-dist, and alsorequires-devforuv add --dev.===declaration, a URL, or no declaration), the revert writes neither file. It drift-keeps both, and the warning says what to change. That matches the issue's fail-closed alternative and the PEP 723 script lane.uv.lockwould still route through the uuid dir while the revertedpyproject.tomlno longer does, nothing is written. A half-reverted uv pair can't be written any more.vendor --check: for a PyPI entry that nothing installs from but some project file still names, the verdict iswiring incomplete: … but uv.lock still resolves through it …with a re-lock-first remedy. It no longer says "no lockfile or config references …".Root cause
revert_uv(crates/socket-patch-core/src/vendor/pypi_uv.rs) restores only the fragments the ledger recorded. A transitive package has no rootrequires-distelement when it is vendored, so nothing is recorded for it. The elementuv addwrites later through the socket-written[tool.uv.sources]entry was therefore never restored. The pair was also written without checking that the reverted lock no longer routes through the wheel. The residual-reference guard inpypi.rsruns only after the write, so it could keep the artifact but couldn't stop the inconsistent write.Test evidence
Each new test was run red with the fix disabled (the new blocks short-circuited), then green with it:
uv add six==1.16.0(requires-dist)pypi_uv::tests::revert_after_promoting_a_transitive_to_direct_respells_the_root_entryuv add --dev(requires-dev)pypi_uv::tests::revert_after_promoting_a_transitive_to_a_dev_group_respells_itpypi_uv::tests::revert_keeps_both_files_when_an_unrecorded_reference_survives(multi-clause,===, undeclared, another name routed through the wheel)uv add six==1.16.0,vendor --revert,uv lock --check)e2e_vendor_pypi_build::uv_vendor_revert_after_promoting_the_transitive_to_direct(uv 0.11.32)vendor --checkclaims no file references the dirmode_migration_pypi::vendor_check_names_the_file_of_a_half_unwound_uv_pairremoveandrollbackcall the samerevert_uv, so the same fix covers them.Commands run locally:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test --workspace --all-features: 7,285 passed. 13 failed, all sandbox-only and outside the code this PR touches: 12 write-failure tests whosechmod-made read-only dirs don't block writes because the sandbox runs as root (copy_tree::relax_loop_must_not_traverse_symlinked_root,covgap_commands_vendor×3,partial_lockfile_write_failure…,redirect_json_mode_write_failures…,vlt::…heal_invalidation_failure_warns,repair_…_unremovable/cleanup_failure×2, and corecopy_tree/vlt_heal/pypi_poetry/pypi_requirementswrite-failure tests), plus 1 that needs network to the patch API (e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed). CI runs them as non-rootcargo test -p socket-patch-cli --all-features --test e2e_vendor_pypi_build -- --include-ignored uv_vendor_revert_afterwithSOCKET_PATCH_UV_E2E_REQUIRED=1: 7 passed (every existing uv relock-then-revert capstone plus the new one)The npm / PyPI / gem wrappers only dispatch to the binary, so they need no change.
/code-review high
Fixed in
b022bef:===and other underivable declarations are no longer restored without a specifier; the keep-both remedies no longer suggest auv lockthat would rebuild the same element;vendor --checkuses the probe's clause instead of stripping an English suffix;lock_requirement_declaredwas folded intodeclared_lock_specifier;drift_keptis now a plain bool; the doc comment lists the new verdict.Not fixed:
pypi_reference_clause, which probespyproject.toml) keeps the artifact and the ledger entry, so nothing is deleted. A precise TOML-level route check would add a lot of code for a far-fetched input.rewrite_root_metadata_entries. Sharing it means refactoring the wire path as well, which is out of scope for this fix.path, thenwith_specifier). This only runs on the rare promoted element, and reusingwith_specifierkeeps key placement in one place.🤖 Generated with Claude Code
https://claude.ai/code/session_019iinbVuLMEUcDSE3PWUSho
Generated by Claude Code