Skip to content

Fix vendored revert reading a removed dependency as drift (#1132, #1140, #1142) - #1147

Merged
Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-vendored-removed-dep-drift
Oct 8, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-vendored-removed-dep-drift

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #1132
Fixes #1140
Fixes #1142

Summary

After the package manager removes a vendored dependency, the bun.lockb, uv and Pipenv vendored reverts now see that nothing is left to restore and finish the revert. Before, they called it drift and kept the artifact and ledger entry forever. So scan --prune, vendor --revert, remove and rollback now clean up after bun remove, uv remove and pipenv uninstall --categories <x>, and vendor --check goes back to green instead of looping on its scan --prune remedy.

Root cause (shared)

The npm-family text backends already treat a recorded lock entry that has vanished as vendor_lock_entry_removed (#665): the user removed the dependency, so there is nothing to restore, and the artifact goes once nothing references its uuid dir. Three backends never got that arm. They mapped the vanished entry to vendor_lock_entry_drifted, and RevertOutcome::drift_skipped() then keeps the artifact and the ledger entry:

Issue Backend Bad arm on main
#1132 vendor::bun_binary::revert Neither the rewritten nor the original snapshot is in bun.lockb → "binary package resolution has drifted"
#1140 vendor::pypi_uv::revert_uv The uv_lock_package / uv_lock_requires_dist arms count only "original present" as converged
#1142 vendor::pypi_pipenv::revert_pipenv A missing category key (Pipenv 2022/2023 drop an emptied category) hits drifted() before the missing-entry retire arm

Fix

Each backend now uses the same rule. If the record's entry is gone and nothing in the project's lock files still names the entry's uuid, there is nothing to restore and no install that needs the artifact, so the record is not drift.

  • bun.lockb: the check runs once before any restore. When no package resolution names the uuid, the record warns vendor_lock_entry_removed. An unreadable package table fails closed (drift).
  • uv: the check runs once before any record is reverted, against pyproject.toml and uv.lock. A record whose written fragment carried the uuid warns vendor_lock_entry_removed instead of drift. This applies to the package unit, requires-dist, respell failures and the whole-array records.
  • Pipenv: a missing category retires the record (vendor_lock_entry_relocked), the same way the existing missing-entry arm does. Both arms now also require that nothing left in Pipfile.lock names the uuid. Before, the missing-entry arm retired a record even when its entry had been moved to another category that still routes through the wheel.

A lock that still routes through the uuid dir stays drift in every backend, and the regression tests assert that negative case.

This also fixes the upgrade variant noted in #1132 for bun.lockb (bun add minimist@1.2.8 after vendoring).

Tests (red on main, green here)

Issue Test Kind Red on main
#1132 vendor::bun_binary::rebuild_tests::revert_after_package_left_the_lock_is_not_drift unit vendor_lock_entry_drifted: binary package resolution has drifted
#1132 e2e_bun_lockb::binary_vendored_revert_after_bun_remove e2e, real Bun 1.4.2 bun remove same, via vendor --revert --json
#1140 vendor::pypi_uv::tests::revert_after_uv_remove_is_not_drift unit (+ still-referenced negative) two vendor_lock_entry_drifted
#1140 e2e_vendor_pypi_build::uv_vendor_revert_after_uv_remove e2e, real uv 0.11.32 uv remove six vendor_lock_entry_drifted ×2 + vendor_revert_kept
#1142 vendor::pypi_pipenv::tests::revert_retires_record_whose_category_a_relock_dropped unit (+ still-referenced negative) drift on the dropped docs category

The existing Pipenv unit test that pinned drift for a deleted section is renamed and now expects the retire.

The bun e2e test is deliberately named outside the native_binary_ prefix. scripts/backtest-bun-lockb.py runs that prefix and only passes when exactly 3 tests pass. The first push used the prefix and turned the binary (*) backtest red; that is fixed in b9802a6.

Local runs

  • cargo clippy --workspace --all-features -- -D warnings: clean. With --all-targets, the only errors are pre-existing ones in files this PR doesn't touch (maven_repo.rs, nuget_feed.rs, crawler_ruby_e2e.rs).
  • rustfmt --check on every changed file: clean. CI doesn't gate on cargo fmt, and main isn't fmt-clean, so I didn't reformat unrelated files.
  • cargo test --workspace --all-features --no-fail-fast: 11,694 passed, 14 failed.
    • 13 of the 14 fail identically on main in this container. They are chmod-based write-failure tests, and the container runs as uid 0, which bypasses the permission.
    • The 14th, in_process_pypi_multi_release::broad_scan_keeps_all_releases, failed in its pip install six setup and passes on re-run.
  • e2e_vendor_pypi_build -- --include-ignored uv_vendor_revert_after: 5/5 pass, including the 4 existing relock-revert cells.
  • e2e_bun_lockb new test: pass on Bun 1.4.2.
  • backtest-bun-lockb.py for 1.0.36, 1.1.45 and 1.4.2: after the rename it runs exactly the 3 pinned tests. The only local failure is native_binary_alias_and_transitive, because this sandbox's proxy returns 403 for the github: tarball that cell installs.
  • e2e_vex_build -- --ignored pipenv:: with Pipenv 2023.12.1 and 2026.8.0: pass.
  • No wrapper changes needed: npm/, pypi/ and gem/ only dispatch to the binary.

CI

ci-ok is green on b9802a6, every check suite on that head completed with no failure, and Cursor Bugbot reviewed b9802a6 with no findings.

Not covered by a new e2e

#1142 has a unit regression test on the exact lock shape Pipenv 2022/2023 write: the category key is dropped from Pipfile.lock. There is no new real-Pipenv pipenv uninstall --categories e2e cell; the existing Pipenv suite has a single end-to-end flow test.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UnFjfjCA3wnfaAtytvyB1E

Assisted-by: Claude Code:claude-opus-5-5
Pipenv 2022 and 2023 delete an emptied category key from Pipfile.lock
when its last package is uninstalled. The vendored revert read that as
drift and kept the wheel and ledger entry forever, so vendor --check
stayed red and its scan --prune remedy looped (#1142).

A missing category now retires the record the same way a missing entry
already did, and both only when nothing else in the lock still points
at the vendored uuid dir.

Assisted-by: Claude Code:claude-opus-5-5
After uv remove of a vendored package, uv deletes the dependency, its
[tool.uv.sources] line and every uv.lock fragment that pointed at the
vendored wheel. The revert read the missing package unit and
requires-dist element as drift, so scan --prune, vendor --revert,
remove and rollback all kept the wheel and ledger entry and
vendor --check stayed red (#1140).

When neither pyproject.toml nor uv.lock names the entry's uuid any
more, a record whose written fragment carried that uuid now warns
vendor_lock_entry_removed instead, so the revert finishes. A lock that
still routes through the wheel stays drift.

Assisted-by: Claude Code:claude-opus-5-5
After bun remove of a vendored package in a bun.lockb project, neither
the vendored nor the pre-vendor record is left in the lock. The revert
called that drift, so scan --prune, vendor --revert and remove kept
the tarball and ledger entry, and vendor --check stayed red (#1132).
The text bun.lock and npm backends already handled this.

When no package in bun.lockb resolves through the entry's uuid dir,
the record now warns vendor_lock_entry_removed and the revert
finishes. The same fixes an upgrade off the patched version.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) force-pushed the agent/fix-vendored-removed-dep-drift branch from 3087ae8 to c8b41d8 Compare October 8, 2026 16:39
Runs uv remove on a vendored dependency and checks vendor --revert
retires the entry, deletes the wheel and leaves a pair uv lock --check
accepts (#1140). Fails on main with the drift-kept output from the
issue.

Assisted-by: Claude Code:claude-opus-5-5
Vendors minimist into a real binary bun.lockb, runs bun remove, and
checks vendor --revert retires the entry and deletes the tarball
(#1132). Fails on main with "binary package resolution has drifted".

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

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

scripts/backtest-bun-lockb.py runs every native_binary_ test and
passes a Bun version only when exactly three pass. The new bun remove
regression test used that prefix, so every version in the bun.lockb
backtest failed. Rename it; it still runs in the e2e_bun_lockb suite.

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

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

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at head b9802a65f8.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Dequeued on a merge-queue CI failure that isn't caused by this PR. It needs a re-queue.

  • Failing check: test (windows-latest, 2) in merge-group run 37837758193. One test failed: e2e_redirect_gem_stale_install::gem_hosted_global_gemfile_setting_is_refused. The hosted gem scan on Windows did not emit the expected redirect_gem_bundle_gemfile_unsupported refusal.
  • Why it isn't this PR's: this PR only changes the bun.lockb, uv and Pipenv vendored revert backends. It touches no gem or redirect code.
  • The same code passed: the merge group stacked directly on this one (pr-1148, run 37837761511) contained this PR's changes plus Fix sbt docker e2e flake on Maven Central blips #1148's, and finished success, including the same Windows test job.
  • Fix: none exists yet. The test is intermittent on Windows and is unrelated to this change.

The PR head is unchanged at b9802a6: ci-ok is green, Bugbot is clean, and it is mergeable. A re-queue should be all it needs.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[final reviewer] Re-enqueued (auto-merge on, squash) at head b9802a65. It was evicted at 20:36 UTC on the Windows gem e2e failure noted above, which isn't this PR's. This is the one re-queue.


Generated by Claude Code

Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 8, 2026
gem_hosted_global_gemfile_setting_is_refused only reached the redirect
stage because the scan found the host's globally installed gems via
`gem env`: the refused lock contributes no packages, so without an
installed package no batch call fires and the refusal never runs.

On Windows runners `gem env` sometimes outlives the 10s probe budget.
The scan then reports scannedPackages: 0 and the test fails. This
evicted two merge-queue entries on 2026-10-08 (#1147 and one at
17:31 UTC).

Lay the gem down in the project with materialize_installed_gem, as
the other tests in this file do, so the test no longer depends on
the host's Ruby install.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xr7gMxM5ugBStCpk6kJ3V4
Merged via the queue into main with commit 9a79e75 Oct 8, 2026
413 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-vendored-removed-dep-drift branch October 8, 2026 22:55
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 8, 2026
Resolve the bun_binary.rs revert conflict with #1147 (#1132): keep this
branch's migrated bun.lock handling and main's "dependency removed, not
drift" probe. The probe reads the binary lock's package table, so it is
computed only for RevertLock::Binary (a migrated text lock keeps its own
LOCK_ENTRY_REMOVED path), and the migrated early returns now yield
Ok(false) to match main's Result<bool> restore closure.

Co-Authored-By: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 9, 2026
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
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 9, 2026
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
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 9, 2026
#1147 landed a bun.lockb revert that calls bun_lock::revert_one_record
for a migrated text record, and main moved npm_lock's npm_origin
import. Both broke against this branch in the merge queue (clippy:
E0061, E0425). Pass `false` for the new default-registry argument from
the bun.lockb path, which keeps a moved default-registry tuple there as
drift: bun.lockb stays outside the #1155 upgrade path. Import
legacy_packages_key again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mo7HM9gyRkUAMxWqi62Dxz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment