Skip to content

Fix vendored Pipenv pylock.toml co-wiring (#1368) - #1399

Open
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
mainfrom
agent/fix-pipenv-pylock-cowire
Open

Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
mainfrom
agent/fix-pipenv-pylock-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

Fixes #1368

Summary

With [pipenv] use_pylock = true, pipenv lock writes a pylock.toml beside Pipfile.lock. PEP 751 installers (uv pip install -r pylock.toml, pip) install from it. Vendored mode now wires that pylock to the same committed wheel as Pipfile.lock, so vendor --check and vex stop reporting contested wiring and installs from the pylock get the patched package.

This finishes #1368: its uv-export lane was fixed by #1390, and this PR covers the remaining Pipenv pylock lane.

Root cause

detect_pypi_flavor routed a Pipenv project with both locks to Pipfile.lock (#1122), and named a pylock that pins the package as a pypi_multiple_lockfiles loser. Nothing ever wired it, so the "rewire both locks" remedy looped. Pipenv co-wiring (#1309, #1390) covered only an exported requirements.txt.

Change

  • pypi_lock::pipenv_pylocks classifies each Pipenv pylock that holds the package: on the registry (wirable), already on a vendored wheel (with its uuid), or unwirable (a symlink, a non-registry source). Hatch-derived locked-env pylocks are left out.
  • Fresh vendor: the pylock is wired through the python-lock backend after Pipfile.lock and before the requirements export. Its records sit in the same ledger entry (python_lock_document). A refusal puts back every file already written.
  • Re-run over an already wired Pipfile.lock: a pylock made since is wired to the committed, verified wheel and recorded once. A re-lock replaces the old record instead of adding a second one.
  • Superseding patch: the old entry's pylock records are reverted first, then the pylock is wired fresh, so the pre-vendor text carries forward. If that pylock drifted, the run refuses (as a drifted Pipfile.lock already does), and --dry-run predicts that refusal.
  • Revert: the export and the pylocks are reverted before Pipfile.lock, and put back if any later revert fails or drift-keeps. The tree never ends up half reverted.
  • A pylock that is unwirable, or that points at another patch's wheel that no ledger entry records, is still named in pypi_multiple_lockfiles.
  • Dry run previews the wiring (pypi_pipenv_pylock_wired).
  • Repair reads a Pipenv entry's Pipfile.lock fragment directly, so a pylock record can't hide it.
  • Docs updated: the CLI_CONTRACT pypi_multiple_lockfiles row and the pipenv-compatibility table.

Test evidence

Red on unchanged code (cargo test -p socket-patch-core --lib -- vendor::pypi::tests::pipenv_: 6 failed), green with the fix:

Test Covers
pipenv_vendor_wires_the_pipenv_pylock fresh vendor wires pylock (with and without an export); discovery uncontested; revert restores byte for byte
pipenv_pylock_dry_run_previews_the_wiring dry-run preview, nothing written
pipenv_in_sync_rerun_wires_a_later_pylock_once pylock made after vendoring; re-lock replaces the record
pipenv_superseding_uuid_rewires_the_pylock supersede re-wires; revert restores registry
pipenv_pylock_revert_keeps_both_wired_when_the_lock_fails no half revert on lock failure
pipenv_pylock_beside_pipfile_lock_routes_to_pipenv (updated) / pipenv_use_pylock_project_vendors_into_pipfile_lock (updated) no loser warning; pylock wired and reverted
pipenv_symlinked_pylock_beside_pipfile_lock_is_a_loser unwirable pylock still named
pipenv_pylock_probe_skips_a_fifo_hatch_file, pipenv_pylock_on_an_unrecorded_patch_is_named, pipenv_superseding_uuid_refuses_a_drifted_pylock_in_dry_run_too, pipenv_pylock_drift_keeps_every_file_wired, pipenv_leaves_a_hatch_locked_env_pylock_alone review hardening
recover_pypi_urlless_locks_report_no_fetchable_url (extended) repair with a pylock record in either order
e2e pipenv::pipenv_pylock_projects_wire_what_pipenv_installs (real Pipenv 2026.8.0) pylock wired; fresh pipenv install --deploy patched; uv pip install -r pylock.toml installs the patched six

Commands run locally:

  • cargo fmt --all -- --check: ok
  • cargo clippy --workspace --all-features -- -D warnings: ok
  • cargo test -p socket-patch-core --all-features --lib: 6306 passed. 4 failed, and the same 4 fail on main in this sandbox because they rely on file permissions that root bypasses (copy_tree::relax_loop_must_not_traverse_symlinked_root, vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry, pypi_poetry::wire_write_failure_*, pypi_requirements::wire_failure_rolls_back_*).
  • cargo test -p socket-patch-cli --all-features --test mode_migration_pypi: 58 passed
  • SOCKET_PATCH_PIPENV_E2E_REQUIRED=1 SOCKET_PATCH_PIPENV_E2E_VERSIONS=2026.8.0 cargo test -p socket-patch-cli --all-features --test e2e_vex_build -- pipenv:: --ignored: 2 passed
  • A full cargo test --workspace ran out of the sandbox's disk while linking. CI runs the full matrix.

No wrapper changes are needed: npm/, pypi/ and gem/ only dispatch to the binary.

/code-review high findings not fixed

Compatibility

An older binary reverting a Pipenv entry that has pylock records gives the python_lock_document records to the Pipfile.lock backend, which reports them as drifted and keeps the entry. That is the ledger's documented forward-compatibility behavior.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WU8SL8CcLJsyMHUF4HRaTn


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
With `[pipenv] use_pylock = true`, `pipenv lock` writes a pylock.toml
beside Pipfile.lock, and PEP 751 installers (`uv pip install -r
pylock.toml`, pip) install from it. Vendored mode wired only
Pipfile.lock and named the pylock 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 Pipenv project now also points the pylock's entry for the
package at the same committed wheel. The ledger entry records it,
revert restores it, a superseding patch re-wires it, and a re-run over
an already wired Pipfile.lock wires a pylock made since. A pylock that
can't be rewritten in place (a symlink, a non-registry source) is still
named by pypi_multiple_lockfiles.

Refs #1368

Assisted-by: Claude Code:claude-opus-5-5
A Pipenv entry now also records the pylock wired beside Pipfile.lock.
Repair stopped at that pylock record when it carried no hash-pinned
wheel, instead of falling back to the Pipfile.lock digests it used
before. It now reads the Pipfile.lock fragment as it did.

The real-Pipenv use_pylock e2e now expects the pylock to point at the
vendored wheel too.

Refs #1368

Assisted-by: Claude Code:claude-opus-5-5
- A pylock already pointing at another patch's wheel that no ledger
  entry records is named in pypi_multiple_lockfiles instead of being
  silently treated as wired.
- A superseding patch whose old pylock wiring drifted refuses, as a
  drifted Pipfile.lock does, and --dry-run now predicts that refusal.
- A drifted pylock on revert keeps every file wired (Pipfile.lock and
  the export too), so the tree matches the kept ledger entry instead
  of being half reverted.
- A pylock Hatch derives for locked environments is left alone.
- Repair reads a Pipenv entry's Pipfile.lock fragment whatever order
  its records are in.
- The real-Pipenv e2e now installs from the wired pylock with
  `uv pip install -r pylock.toml` and checks it gets the patched six.

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 21:16
@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_lock.rs
The Pipenv pylock probe read pyproject.toml and hatch.toml with a
plain read, so a FIFO at either path blocked the vendor run forever.
It now uses the same regular-file read as flavor detection.

Refs #1368

Assisted-by: Claude Code:claude-opus-5-5
@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 b5a3675. 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 b5a3675.

  • CI: 35/35 non-skipped checks green (19 skipped).
  • Bugbot: reviewed b5a3675, no findings.
  • Mergeable: yes, no conflicts with main. Already approved by Tanmay Singla (@Tanmay182003) at this head.
  • Slack announcement not sent: the Slack send tool was unavailable this run, so the next run will retry.

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.

Vendored mode leaves a Pipenv-written pylock.toml or a uv-export requirements.txt unpatched beside the wired lock

3 participants