Skip to content

Hosted gem rollback / remove leave the patched .gem in the project's committed vendor/cache, so every later bundle install fails with mismatched checksums (exit 37) while the unwind reports success #1260

Description

[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).

Summary

In hosted mode, a project that commits its gem cache (bundle cache / bundle package, so vendor/cache or a configured cache_path) ends up with the patched .gem archive in that cache. rollback and remove restore Gemfile + Gemfile.lock to the rubygems.org entry, re-pin CHECKSUMS to the upstream sha, and report status: success. They leave the cached archive alone and don't mention it. Bundler installs from the cache first and checks it against the restored upstream checksum, so every later bundle install fails, frozen or not:

Bundler found mismatched checksums. This is a potential security risk.
  paint (2.3.0) sha256=327d623e…   (Gemfile.lock, rubygems.org)
  paint (2.3.0) sha256=0309368a…   (vendor/cache/paint-2.3.0.gem)

(exit 37)

rollback emits only the generic reinstall_required advisory ("installed trees keep their patched bytes until the next package-manager install"), which implies the next install fixes things. Here the next install can't run at all. remove emits no warning. On Bundler 2.5 (no CHECKSUMS), a frozen / deployment install takes the cached archive without complaint, so the project keeps installing the patched bytes after a "successful" rollback, while an unfrozen install fails with exit 37.

The forward direction already handles this exact artifact: scan --mode hosted flags a stale archive in the cache dir with redirect_gem_stale_install and tells the user to delete it (#483). The unwind has no counterpart.

Impact

  • After a rollback, CI (BUNDLE_FROZEN / deployment) and local installs fail until someone finds and deletes vendor/cache/<gem>-<ver>.gem by hand. Bundler's own suggested remedy ("remove the matching checksum in Gemfile.lock and run bundle install") doesn't help: it still fails against the remote checksum.
  • On Bundler < 2.6, frozen installs silently keep the patched gem, so the rollback doesn't actually take effect.

Repro (Linux, Ruby 3.3.6; local mock of the patch API + patch-registry compact index, real rubygems.org upstream)

printf 'source "https://rubygems.org"\n\ngem "paint", "2.3.0"\n' > Gemfile
bundle config set --local path vendor/bundle
bundle lock && bundle lock --add-checksums
socket-patch scan --mode hosted --yes --cwd . $API_ARGS        # redirects paint, CHECKSUMS converged
bundle install                                                 # patched paint installed
bundle cache                                                   # vendor/cache/paint-2.3.0.gem = patched archive (sha 0309368a…)
socket-patch rollback pkg:gem/paint@2.3.0 --json --cwd . $API_ARGS --patch-server-url $MOCK
#  -> status success, hosted.reverted [pkg:gem/paint@2.3.0], warnings [reinstall_required]
#  -> Gemfile + Gemfile.lock byte-identical to the pre-scan pair; vendor/cache still holds the patched .gem
# fresh checkout of Gemfile, Gemfile.lock, .bundle, vendor/cache:
BUNDLE_FROZEN=true bundle install    # exit 37, mismatched checksums
bundle install                       # exit 37, mismatched checksums
# control: rm vendor/cache/paint-2.3.0.gem && BUNDLE_FROZEN=true bundle install   -> exit 0, upstream bytes

Expected vs actual

  • Expected: the unwind leaves an installable project, or tells the user exactly what to delete. CLI_CONTRACT.md "Hosted unwind coverage" (gem) says the pair is restored so it "comes back byte for byte", and the contract's Gem stale-install guard treats a stale archive in Bundler's cache dir as something Bundler would reuse and that must be removed. The reinstall_required row says installed trees are fixed "until the next package-manager install".
  • Actual: status: success, and the patched archive stays in the cache dir. Every install exits 37 on Bundler ≥ 2.6 (and 2.5 unfrozen). Bundler 2.5 frozen installs keep the patched gem.

Matrix (all reproduce; each cell ×1 unless noted)

OS Bundler Lock Unwind Cache dir Fresh frozen install Fresh unfrozen install
Linux 4.0.22 CHECKSUMS rollback (×2) vendor/cache exit 37 exit 37
Linux 4.0.22 CHECKSUMS remove vendor/cache exit 37 exit 37
Linux 4.0.22 CHECKSUMS rollback cache_path gems/cache exit 37 exit 37
Linux 4.1.0.beta2 CHECKSUMS rollback vendor/cache exit 37 exit 37
Linux 2.6.9 CHECKSUMS rollback vendor/cache exit 37 exit 37
Linux 2.5.22 no CHECKSUMS (converged by bundle install) rollback vendor/cache exit 0, patched bytes installed exit 37
Linux 4.0.22 CHECKSUMS rollback, no cache (control) — exit 0, upstream —
Linux 4.0.22 CHECKSUMS rollback, cached archive deleted (control) vendor/cache exit 0, upstream —

macOS / Windows not probed. The behaviour is OS-independent (lock text and a cache file).

Suspect code

  • crates/socket-patch-core/src/patch/redirect/upstream/gem.rs:596 (restore) rewrites only the manifest + lock and never looks at Bundler's cache dir.
  • crates/socket-patch-cli/src/commands/rollback.rs:1497 emits only the generic reinstall_required. The cache-dir resolution already exists for the scan-side guard (crates/socket-patch-cli/src/commands/scan/hosted.rs:237, the vendor/cache flavor of redirect_gem_stale_install, which honours cache_path since Fix Bundler settings resolution order (#483, #507) #532) and could feed a matching warning, or a deletion, on unwind.

Not bisected: hosted gem unwind was added in v5 and has never touched the cache.

Activity

  1. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triage: priority:p1 (Bundler). Not a duplicate. #1109 and #1098 cover the scan side, which flags a stale system gem-home copy. This issue is about the unwind side: rollback/remove leave a patched archive in Bundler's cache dir. No open or merged PR addresses it, and restore in patch/redirect/upstream/gem.rs on main still rewrites only Gemfile + Gemfile.lock.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (shared root cause: the hosted gem unwind restores Gemfile + Gemfile.lock but never checks Bundler's cache dir for the patched archive). Branch: agent/fix-gem-unwind-stale-cache. Claim-ID: 2026-10-09T11:22:03Z-5ccfee


    Generated by Claude Code

  3. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1263


    Generated by Claude Code

  4. added 7 commits that reference this issue on Oct 9, 2026
    67ea66a
    95c3d7e
    aae817d
    0d63c93
    fd89ba1
    b42a6bd
    9415826
  5. added
    v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.
    uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.
    compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.
    on Oct 9, 2026
  6. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    v5 release blocker (P1). Routine Bundler rollback with committed vendor/cache must give an effective cache/reinstall remedy so the next bundle install works. PR #1263 is pending.

    This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claimedagent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentcompatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.pm:bundlerBundler (RubyGems)priority:p1uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions