Skip to content

Fix vendored uv export requirements.txt (#1368) - #1390

Open
Mikola Lysenko (mikolalysenko) wants to merge 6 commits into
mainfrom
agent/fix-pypi-sibling-lock-cowire
Open

Mikola Lysenko (mikolalysenko) wants to merge 6 commits into
mainfrom
agent/fix-pypi-sibling-lock-cowire

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Refs #1368 (lane 2, the uv export requirements.txt; lane 1, Pipenv's
pylock.toml, is a follow-up slice, see below)

Root cause

PR #1309 taught vendored Pipenv to co-wire an exact registry pin in an
exported requirements.txt into the same ledger entry as Pipfile.lock.
The uv flavor never got the same treatment: a uv export requirements.txt
beside uv.lock was only named as an unpatched loser
(pypi_multiple_lockfiles). vendor --check and vex then reported the
wiring as contested, and re-running vendor changed nothing, so the
suggested remedy looped. Hosted mode already rewrites both files.

Fix

The #1309 sibling wiring is now flavor-generic and shared by Pipenv and
uv:

  • detect_pypi_flavor: an exact, rewritable pin beside uv.lock is no
    longer a loser (same predicate, sibling_pin_wirable, as Pipenv).
  • Fresh vendor: WiringPlan::Uv carries a sibling flag; after
    wire_uv the export's pin goes to the same wheel. If that refuses, the
    uv pair is restored so neither file is left half wired
    (wire_sibling_requirements, shared with Pipenv).
  • In-sync re-run (export made after vendoring, or re-exported):
    wire_in_sync_sibling (was wire_in_sync_pipenv_sibling) wires the
    export to the verified committed wheel and replaces, never duplicates,
    that file's records.
  • Superseding patch: the old entry's revert restores both files, and the
    fresh plan re-detects the export and wires it to the new wheel.
  • Revert: revert_with_sibling (was revert_pipenv_with_sibling)
    reverts the export lines, then the lock records, restoring the export
    if the lock revert fails. Dispatched for both uv and pipenv.
  • Pins the requirements wiring cannot rewrite (a range, a UTF-16 export)
    stay loud, as before.
  • CLI_CONTRACT.md pypi_multiple_lockfiles row updated.

No wrapper (npm/, pypi/, gem/) changes: this is core vendor logic.

Tests (red on main, green here)

Case Test
fresh vendor wires uv.lock + export, discovery not contested, revert restores all 3 files byte for byte vendor::pypi::tests::uv_vendor_wires_the_exported_sibling_requirements
in-sync re-run wires a later export once (twice, no duplicate record) uv_in_sync_rerun_wires_a_later_export_once
superseding uuid re-wires both, revert restores registry pins uv_superseding_uuid_rewires_the_export
detect: exact pin beside uv.lock is no loser, a range still is requirements_beside_the_governing_lock_is_a_loud_loser (extended)
CLI: vendor, vendor --check exit 0, settled re-run, vendor --revert restores mode_migration_pypi::uv_requirements_export_is_wired_with_the_lock
symlinked export stays a loser, uv.lock still vendored (review finding) uv_symlinked_export_stays_a_loser
fresh --dry-run previews the export wiring, writes nothing uv_dry_run_previews_the_export_wiring
hosted takeover refuses a uv/Pipenv export pin in a -r include patch::redirect::pypi_takeover::tests::sibling_export_in_an_include_is_refused
a drift-kept uv.lock revert keeps the export wired (Bugbot) uv_drifted_lock_keeps_the_export_wired
rollback snapshot leaves unreadable files alone (Bugbot) snapshot_skips_unreadable_files
CLI: a UTF-16 export still contests (#1120) uv_requirements_export_contests_the_wiring_in_utf16 (UTF-8 case moved to the test above)

All four unit tests failed before the fix (requirements.txt left unpatched, no entry on the in-sync re-run, export still on the old
uuid) and pass after it. mode_migration_pypi: 55/55 pass.
cargo fmt --check and cargo clippy --workspace --all-features -D warnings are clean.

Local runs on 9a0f05f

  • cargo fmt --all -- --check: clean. cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo test --workspace --all-features --no-fail-fast: everything passes except 13 write-failure / unremovable-file tests in 5 targets (covgap_commands_vendor, e2e_bun_lockb, in_process_redirect, repair, core lib). The sandbox runs as uid 0, so chmod can't make those writes fail. I re-ran the same targets on unmodified origin/main and got the identical failure set, so they come from the environment, not this diff.
  • e2e_vendor_pypi_build --include-ignored with uv 0.11.32 (SOCKET_PATCH_UV_E2E_REQUIRED=1): 52/52 pass.

/code-review high

All nine findings were handled in 9a0f05f except two. The double sibling_pin_wirable walk (once in detect, once in the prelude) is the same pattern Pipenv already has, and it costs one extra read of the small requirements tree. I left it alone so this PR stays scoped. The "per-flavor descriptor" suggestion is covered by exhaustive matches (sibling_lock, revert_lock) rather than a new type.

Follow-up slice: Pipenv use_pylock = true (#1368 lane 1)

pylock.toml beside Pipfile.lock needs the pypi_lock backend
co-wired the same way (fresh / in-sync / in-place supersede / revert by
record kind). It's a different wiring backend, so it's left out of this
PR to keep it reviewable. #1368 stays open for it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01645q8Fr6CEo5KdPqh3Ne7t


Note

Medium Risk
Changes PyPI vendoring, revert, and lockfile discovery for a common Docker/pip install path; mistakes could leave half-wired locks or unpatched installs, but behavior mirrors existing Pipenv sibling wiring with rollback and extensive tests.

Overview
Vendored uv projects now co-wire a root requirements.txt from uv export with uv.lock to the same committed wheel—the same sibling pattern Pipenv already had for pipenv requirements (#612, #1368). That removes false pypi_multiple_lockfiles / contested wiring for rewritable exact pins, aligns vendored mode with hosted, and fixes the remedy loop where re-run vendor did nothing.

Implementation generalizes Pipenv-only helpers: flavor detection skips the loser warning when sibling_pin_wirable passes for uv.lock; fresh vendoring runs wire_uv then wire_sibling_requirements with rollback of the uv pair on refusal; in-sync re-runs use wire_in_sync_sibling; revert uses revert_with_sibling for both uv and pipenv. Hosted takeover preflight refuses exported pins wired only in -r includes. Unrewritable exports (UTF-16, ranges, symlinked files) still warn or fail as before. CLI_CONTRACT and broad unit/CLI tests cover dry-run, supersede, drift-kept revert, and discovery.

Reviewed by Cursor Bugbot for commit c1f669b. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A uv project often keeps a requirements.txt made by `uv export` for
Docker or plain pip installs. Vendored mode wired only uv.lock and
named the export as an unpatched install source, so `vendor --check`
and `vex` reported contested wiring, and re-running vendor changed
nothing, so the suggested remedy looped.

Vendoring a uv project now also rewrites an exact registry pin of the
package in requirements.txt (or an in-root -r include) to the same
committed wheel, as already done beside Pipfile.lock (#612). The
ledger entry records both, revert restores both, a superseding patch
re-wires both, and a re-run over an already wired uv.lock wires an
export made since. Pins the requirements wiring cannot rewrite (a
range, a UTF-16 file) stay named by pypi_multiple_lockfiles.

The Pipenv and uv paths now share one helper for the sibling wiring
and one for its revert.

Refs #1368

Assisted-by: Claude Code:claude-opus-5-5
The pypi_multiple_lockfiles row now says an exact pin in a uv export
requirements.txt is wired with uv.lock, as one beside Pipfile.lock is,
and the #1120 CLI test keeps covering the UTF-16 exports that the
requirements wiring still refuses.

Refs #1368

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title Fix vendored sibling locks for pylock and uv (#1368) Fix vendored uv export requirements.txt (#1368) Oct 11, 2026
- A symlinked requirements.txt or -r include can't be rewritten in
  place, so it is no longer treated as wirable. Those projects vendor
  the lock and name the export as before, instead of failing the run.
- A hosted takeover of a uv or Pipenv entry whose export pin sits in a
  -r include is refused before the revert, as it already is for a
  requirements-flavor entry, since hosted mode re-pins only the root
  requirements.txt.
- A fresh --dry-run now previews the export wiring.
- Rollback after a refused export reuses restore_snapshot, so a file
  that can't be put back is named in the error.
- The flavor matches for the sibling lock and its revert are
  exhaustive, and the doc comments no longer say Pipenv only.

Refs #1368

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 11, 2026 15:24
@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.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/vendor/pypi.rs
Comment thread crates/socket-patch-core/src/vendor/pypi.rs
- revert_with_sibling reverted the export, then let a drift-kept lock
  revert (uv's pair gate writes neither file) leave uv.lock wired. The
  export is now put back on the wheel too, matching the kept entry, so a
  re-run reverts both once the drift is undone.
- snapshot_files recorded an unreadable file as absent, so a rollback
  could delete it. Only NotFound is recorded as absent now; anything
  else unreadable is left out and left alone.

Refs #1368

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01645q8Fr6CEo5KdPqh3Ne7t
@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 c1f669b. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Ready for review at c1f669b.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does. Generalises the Pipenv "sibling requirements.txt" co-wiring (#612/#1309) to any lock flavor and turns it on for uv: detect, fresh vendor, in-sync re-run, supersede and revert all now handle a uv export requirements.txt next to uv.lock, pointing its exact registry pins at the same committed wheel and recording them in the same ledger entry. Fixes #1368 (the Docker pip install -r path otherwise kept installing the unpatched registry wheel). Hosted takeover gets a preflight that refuses an export pin that only lives in a -r include.

Risk: medium. It changes vendor, revert and detect on a common install path, and Pipenv moved too (shared helpers). It mirrors the proven Pipenv pattern, rollback is layered (uv pair restore, then the outer supersede snapshot), and test coverage is broad.

Look here

  • vendor/pypi.rs:397: detect no longer calls a wirable export beside uv.lock a loser
  • vendor/pypi.rs:1682-1730: fresh uv wiring + pair restore if the export refuses
  • vendor/pypi.rs:2593, :2643-2716: wire_sibling_requirements, revert_with_sibling (drift-keep restore at 2706)
  • vendor/pypi_requirements.rs:283:`` sibling_pin_wirable now refuses symlinks
  • patch/redirect/pypi_takeover.rs:45:``` sibling takeover preflight

Verified. Read the full diff and traced partial failure (export refusal restores the uv pair; supersede snapshot covers the export), path safety (ledger paths validated by superseded_files; revert only writes bytes it read), idempotency (wired export makes sibling_pin_wirable false; in-sync re-export replaces, not duplicates, records), revert ordering and ledger flavor matching. Ran cargo test -p socket-patch-core --lib vendor::pypi (426 passed; 2 chmod-based write-failure tests can't fail as root, unrelated), --lib pypi_takeover 7/7, -p socket-patch-cli --test mode_migration_pypi 55/55. CLI_CONTRACT.md row change matches the code. No debug leftovers; CHANGELOG untouched. CI 53/53 on c1f669b, Cursor Bugbot success.

Changes I made. None.

Open questions (non-blocking).

  • Pipenv behavior moves too: a symlinked Pipenv export is now a named loser instead of a refusal; a drift-kept Pipenv lock revert now leaves the export wired (previously reverted while the entry was kept); hosted takeover refuses Pipenv export pins that sit in a -r include. All reasonable, worth knowing.
  • No test covers the rollback branch where wire_uv succeeds and wire_sibling_requirements then refuses; the replacement uv wiring test is LF-only (the removed contest case was CRLF). Good follow-up tests.
  • In revert_with_sibling, vendor_lock_entry_drifted is reused when restoring the export fails; a distinct code would be clearer.
  • Overlaps with Fix requirements.txt pin grammar split (#1365) #1381 in vendor/pypi_requirements.rs; whichever merges second may need a main merge.

Auto-merge is armed: 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

Development

Successfully merging this pull request may close these issues.

2 participants