Repository navigation
Fix gem supersede on Bundler < 2.2 locks (#1688) - #1690
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Bundler 1.17-2.1 lock every rubygems source into one merged GEM section, so the Socket remote an earlier redirect added is tied to the gem only through the Gemfile's `source ... do` block. A superseding patch (or a rotated grant) moved that block to the new URL but left the lock's remote on the old one. Lockfile discovery then saw two different patches and the scan skipped the new one as redirect_unattributable on every run, exiting 0 with the project stuck on the older patch. The hosted gem rewriter now moves that merged section's Socket remote to the new URL in place, alongside the Gemfile block, when the lock has exactly the shape a converged earlier redirect leaves. Every other lock shape is left as before. Fixes #1688 Assisted-by: Claude Code:claude-opus-5-5
The unconverged-supersede capstone assumed every bundler leaves the lock on the old patch after a superseding scan. On bundler < 2.2 the merged GEM section now moves with the Gemfile block, so assert that the new patch lands there (frozen install accepts the pair) and that rollback and remove still unwind it. Refs #1688 Assisted-by: Claude Code:claude-opus-5-5
Review follow-up. The merged-section refresh matched any Socket remote in the section, so a first redirect of one gem could take over another redirected gem's remote. It now moves only the remote that equals this gem's own existing Gemfile source block URL. The frozen-install caveat is now dropped only for locks bundler < 2.2 wrote, the era the e2e proves frozen-installable; a later bundler that kept a merged lock still gets the caveat. The remote line rewrite is shared with the CHECKSUMS-era refresh. Refs #1688 Assisted-by: Claude Code:claude-opus-5-5
Clippy flagged the negated boolean; name the condition instead. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
1 similar comment
|
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 f26a4a0. Configure here.
|
[final reviewer] A heads-up for whoever resolves conflicts here. #1687 (now Generated by Claude Code |
|
[burn-down] Labeled
Generated by Claude Code |
|
Labeled Ready for review (burn-down agent).
Generated by Claude Code |
Final review briefWhat it does. On a Bundler 1.17–2.1 lock (one merged Risk: low–medium. The new path triggers only on a narrow, guarded shape: spec listed once, in a multi-remote section, exactly one remote equal to this gem's own prior block URL, and a Look here
Verified
Changes I made: none. Open questions: #1687 edits the same e2e test's < 2.2 branch to assert the old refusal (to green the nightly). This PR makes that branch assert the supersede lands, so the two conflict. If you approve both, whichever merges second needs a merge that keeps this PR's branch. Once this lands, #1687 is redundant and is probably best closed rather than merged. Auto-merge is armed, so approving sends it straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1688
Summary
On a project whose
Gemfile.lockwas written by Bundler 1.17–2.1, a hostedscanthat found a superseding patch (or a rotated grant) for an already-redirected gem skipped it asredirect_unattributableand exited 0. Every later scan did the same, so the project stayed on the older patch. The scan now applies the new patch. It moves the Gemfilesource … doblock and the lock's matching Socket remote together, and the pair stays frozen-installable.Root cause
Bundler < 2.2 writes every rubygems source into one merged
GEMsection. Lockfile discovery (vex::discover::gem) ties a spec in such a section to a Socket patch only when the lock's Socket remote and the Gemfile'ssource … doblock name the same URL. The hosted gem rewriter (rewrite_gem) updated the Gemfile block in place to the new URL but never touched a pre-CHECKSUMS lock. The pair then named two different patches. The pre-write attribution gate inhosted::enginevetoed the new pin and wrote nothing.Fix
formats::gem::hosted::refresh_merged_gem_lock_remotechanges the merged section's remote that equals this gem's own existing Gemfile block URL to the new index URL, in place. It keeps the line ending and records aredirect_gemfile_lock_source_urledit. It acts only on the shape a converged earlier redirect leaves: spec listed once, in a merged section, that URL listed once, and a!source pin in DEPENDENCIES. For any other shape it changes nothing, so a first redirect into a merged lock behaves as before.rewrite_gemcalls it when the CHECKSUMS-era convergence doesn't apply. Theredirect_gem_frozen_installcaveat is dropped only when the lock'sBUNDLED WITHis < 2.2, which is the era the e2e proves frozen-installable. A merged lock that a later bundler kept still gets the caveat.refresh_remote_line), shared with the CHECKSUMS-era refresh.e2e_redirect_gem_build::gem_hosted_unwind_after_unconverged_supersede_undoes_the_new_block: on bundler < 2.2 it now asserts that the superseding patch lands (Gemfile and lock both on gen 3, frozen install accepts the pair), then still runs therollback/removeunwind. The 2.2+ arm is unchanged.Wrappers (
npm/,pypi/,gem/) only dispatch to the binary, so they need no change.Test evidence
hosted::engine::tests::merged_section_supersede_moves_the_lock_remote_with_the_gemfile(superseding patch + rotated grant, LF + CRLF)redirect_unattributableskipe2e_redirect_gem_build::gem_hosted_unwind_after_unconverged_supersede_undoes_the_new_blockon real bundler 2.1.4formats::gem::hosted::tests::{merged_section_socket_remote_follows_the_new_index_url, merged_section_refresh_refuses_other_shapes, bundled_before_2_2_reads_the_bundled_with_version}Commands run locally:
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test -p socket-patch-core --all-features: 6683 passed, 4 failed. The 4 (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,pypi_requirements::wire_failure_rolls_back_already_written_files) depend on read-only file modes, which don't block root, and this agent container runs as uid 0. None of their files are in this diff, and CI runs them as a normal user.cargo test -p socket-patch-cli --all-features --lib --test e2e_gem --test in_process_gem_apply --test in_process_gem_multi_platform --test in_process_rollback_hosted --test in_process_scan --test e2e_redirect_gem_stale_install: 930 + 22 + 23 + 27 + 27 + 41 + 42 passedBUNDLER_VERSION=2.1.4 SOCKET_PATCH_BUNDLER_E2E_REQUIRED=1 SOCKET_PATCH_BUNDLER_E2E_VERSION=2.1.4 cargo test -p socket-patch-cli --all-features --test e2e_redirect_gem_build -- --ignored: 32 passedA full
cargo test --workspace --all-featuresbuild doesn't fit this agent container's disk allowance, so CI runs the remaining suites. Bundler 1.17.3 isn't runnable on the local Ruby 3.3. The nightlye2e-extended1.17.3 / 2.1.4 / 2.2.33 legs cover it, and PR CI doesn't run those legs.Code review
I ran
/code-review high. Fixed: the refresh could take another redirected gem's Socket remote (it now requires this gem's own block URL, with a regression case); the caveat is now limited to the proven bundler era; stale comments; the duplicated remote-rewrite code; and an invalid test fixture. Not changed:Interaction with #1687
#1687 (test-only, fixes #1685) changes the same e2e test's < 2.2 branch to assert the current refusal. Whichever merges second needs a trivial merge, and this PR's branch (asserting the supersede lands) is the one to keep.
Follow-ups
registry-rewrite gem.ts) should get the same merged-section refresh.🤖 Generated with Claude Code
https://claude.ai/code/session_01RKaRAnSHnVgv3Mxga3ZeAU
Generated by Claude Code
Note
Medium Risk
Changes gem lock rewrite and attribution paths used on every hosted scan; behavior is gated to merged pre-2.2 lock shapes with new tests, but mistakes could still break frozen installs or mis-attribute remotes.
Overview
Fixes superseding hosted gem patches on locks written by Bundler < 2.2, where one merged
GEMsection caused scans to skip the new pin asredirect_unattributablebecause only the Gemfilesource … doblock was updated.When CHECKSUMS-era lock convergence does not apply,
rewrite_gemnow callsrefresh_merged_gem_lock_remoteto retarget the merged section’s Socketremote:that matches this gem’s prior block URL to the new index URL (with strict shape guards so other gems’ remotes are untouched). Remote-line edits are centralized inrefresh_remote_line. Theredirect_gem_frozen_installwarning is suppressed only when that merged refresh succeeds andbundled_before_2_2says the lock is from the proven frozen-installable era.Tests add unit coverage for the refresh helper and
BUNDLED WITHparsing, a hosted-engine supersede/rotated-grant scenario, and extend the e2e unwind test: Bundler ≥ 2.2 still expects Gemfile-on-new / lock-on-old; older Bundler expects Gemfile and lock both on the new patch plus a successful frozenbundle install.Reviewed by Cursor Bugbot for commit f26a4a0. Configure here.
Generated by Claude Code