Repository navigation
Fix gem vendored unwind missing stale cache (#1594) - #1667
Mikola Lysenko (mikolalysenko) merged 5 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A project that ran `bundle cache` while a hosted gem patch was live keeps the patched archive in vendor/cache. After a hosted to vendored takeover, `remove`, `rollback` and `vendor --revert` put the gem back on its upstream lock entry but never named that archive, so fresh installs kept the patched bytes (or failed on CHECKSUMS) while the command reported success. The vendored unwind now runs the same Bundler cache check as the hosted unwind and warns upstream_gem_stale_cache, naming the file. Fixes #1594 Assisted-by: Claude Code:claude-opus-5-5
The takeover's upstream_gem_stale_cache notice said installs fail now, but the takeover then wires the vendored copy, which bundler installs instead. It now says the archive only matters once the gem is unwired, and that the unwind names it again. `scan --prune` reverting a vendored gem whose patch left the manifest runs the same cache check and reports it in gc.warnings[]. Unit tests cover the shared check, and CLI_CONTRACT.md lists where each vendored unwind reports the warning. Refs #1594 Assisted-by: Claude Code:claude-opus-5-5
Without a CHECKSUMS sha, the vendored unwind's cache check fell back to rubygems.org for every gem. A privately sourced gem, which teams often commit to vendor/cache on purpose, would then be flagged on every unwind. The fallback now runs only for gems locked from rubygems.org (or SOCKET_RUBYGEMS_URL); other remotes stay quiet unless CHECKSUMS can judge them. A dry run skips the check: it reads the still-vendored lock, which can't judge the archive. The takeover note now names the unvendoring command generically, and the contract lists the vendor reconcile. Refs #1594 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The vendored unwind's stale-cache check opened Gemfile.lock and gems.locked with a bare read, so a FIFO squatting either path would hang the command. It now uses the same regular-file guard as the hosted restore and skips anything that is not a regular file. Refs #1594 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 3168aa9. Configure here.
|
Ready for review at head
Generated by Claude Code |
Final review briefWhat it does: After a hosted → vendored gem takeover, a Risk: low. The change only adds a warning; nothing is deleted or rewritten. The check returns early unless an archive for the gem is actually in Bundler's cache dir, so the rubygems.org lookup only happens in that case. It is skipped on dry runs and for private-remote gems that have no Look here:
Verified:
Changes I made: none. Open questions: none. The PR description explains the one design choice: Auto-merge is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1594
Summary
Say a project ran
bundle cachewhile a hosted gem patch was live, and later took the patch over as vendored. When the vendored gem is unwound (remove,rollback,vendor --revert), the command now warnsupstream_gem_stale_cacheand names the patchedvendor/cache/<name>-<version>.gem, as the hosted unwind already does (#1260). Before this change it exited 0 with no warning. Fresh frozen installs then kept installing the patched bytes, or failed on CHECKSUMS, while the lock said upstream.Root cause
The Bundler cache-dir check (
stale_cache_warninginpatch/redirect/upstream/gem.rs) ran only inside the hosted restore. The vendored gem revert (vendor/gem.rsrevert_gem_opts) writes the same upstreamGEMentry back, but nothing checked the cache after it. The takeover's advisory was the only notice, and it said installs fail now. That isn't true while the vendored path source shadows the archive.Changes
stale_cache_warningnow takes plain(purl, name, version, lock, client). The newupstream::gem_stale_cache_after_unwind(root, purl, offline)runs the same check after a vendored unwind. It judges the archive against the restored lock'sCHECKSUMSsha, or else the rubygems.org compact index (--offlinenames the archive as unchecked). A gem locked from another remote with noCHECKSUMSsha isn't judged: it can't be checked against rubygems.org, and a committed cache of private gems is normal.VendoredBackend::revertis the shared path behindrollback,remove,vendor --revertandvendor's manifest reconcile. It now adds the warning for a wet gem unwind, next to the existing PyPI reinstall advisory.scan --prune's manifest-dropped leg does the same and forwards it togc.warnings[]. A dry run skips the check, because the lock on disk is still vendored and can't judge the archive.upstream_gem_stale_cacheadvisory a takeover raises now adds that bundler installs the vendored copy while the gem stays vendored, and that the unvendoring command names the archive again.CLI_CONTRACT.md'supstream_gem_stale_cacherow lists the vendored channels. Each one uses its command's existing convention for vendored warnings:warnings[]for rollback, advisory events for remove,vendor --revertandvendor, andgc.warnings[]for prune.No wrapper (
npm/,pypi/,gem/) changes: they only dispatch the binary.Test evidence
remove/rollback/vendor --revertafter a hosted → vendored takeovere2e_redirect_gem_build::gem_vendored_unwind_names_a_patched_archive_in_vendor_cache(realgem build/bundle install/bundle cache, wiremock API;get <uuid> --mode vendoredtakeover, then each unwind)removeexit 0, events onlyremoved/vendor_reverted, noupstream_gem_stale_cacheupstream::gem::tests::vendored_unwind_*(5 unit tests: patched archive named; upstream or missing archive quiet; offline without CHECKSUMS named as unchecked; private remote without CHECKSUMS quiet, but judged when CHECKSUMS exists; a FIFO lock does not block)Commands run locally (Linux, Ruby 3.3.6, Bundler 4.0.18):
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-cli --all-features --test e2e_redirect_gem_build -- --ignored: 31 passedcargo test -p socket-patch-cli --all-features --test e2e_vendor_gem_build -- --ignored: 8 passedcargo test --workspace --all-features: 14,674 passed, 10 failed, none in code this PR touches. Nine are permission-failure tests (read-only files or dirs) that can't fail because this container runs them as root (uid 0):in_process_redirectwrite-failure ×2 plusvlt::scan_redirect_vlt_heal_invalidation_failure_warns,repair×2, corecopy_tree/vlt_heal/pypi_poetry/pypi_requirements. The tenth,e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed, needs the live patch API, which this sandbox can't reach. CI runs as non-root with networkReview notes (
/code-review high)Fixed: the rubygems.org fallback for private-remote gems, the dry-run lock read, the duplicated lock list (now
BUNDLER_LOCKS), the per-entry fullVendorEntryclone, and the takeover note and contract missing the reconcile channel.Not changed, and why:
revert_gem_optsinstead. The vendored revert backends are network-free andRevertOptscarries nooffline. Threading it through every backend's destructuring is a wider change. The check sits in the sharedVendoredBackend::revertpath, exactly where the PyPI reinstall advisory already lives.run_vendor_gcleg (b) is left out on purpose: the gem has left the lock there, so a cached archive no longer installs.Generated by Claude Code