Fix gem modes wiring a manifest Bundler ignores (#341, #390) - #431
Merged
Mikola Lysenko (mikolalysenko) merged 12 commits intoOct 1, 2026
Merged
Conversation
Assisted-by: Claude Code:claude-opus-5-5
This was referenced Oct 1, 2026
Vendored gem mode always wired Gemfile and Gemfile.lock. When Bundler loads a different manifest (a gems.rb twin, or BUNDLE_GEMFILE set in the environment or in .bundle/config), vendor reported success while Bundler installed the unpatched gem, and VEX then attested it. Add one resolver for the manifest Bundler loads, following Bundler's own order, and refuse vendoring before any write unless that manifest is the Gemfile. Refs #341, #390 Assisted-by: Claude Code:claude-opus-5-5
Hosted gem mode picked gems.rb or Gemfile by filename and ignored BUNDLE_GEMFILE. With 'bundle config set --local gemfile Gemfile.next' it rewrote Gemfile, reported success and attested the patch in the in-run VEX, while Bundler kept installing the unpatched gem. The hosted candidate files now hold only the pair Bundler loads. A BUNDLE_GEMFILE naming the project's Gemfile or gems.rb selects that pair; one naming any other file redirects no gem and reports redirect_gem_bundle_gemfile_unsupported. Refs #390 Assisted-by: Claude Code:claude-opus-5-5
Real-Bundler e2e capstones for both issues. A hosted scan of a dual-boot project (BUNDLE_GEMFILE: Gemfile.next in .bundle/config) redirects and attests nothing. Vendor refuses a gems.rb twin and the dual-boot layout, leaving every manifest and lock untouched. Docs, CLI contract and CHANGELOG describe the new refusals. Refs #341, #390 Assisted-by: Claude Code:claude-opus-5-5
A hosted gems.rb project that is then vendored hit the hosted to vendored takeover first: the takeover restored the upstream entry, then vendoring refused gems.rb, leaving the gem unpatched in both modes. The takeover now runs the gem manifest check first, like the yarn berry preflight, so a refused gem keeps its hosted pin. Refs #341 Assisted-by: Claude Code:claude-opus-5-5
Bundler 1.x reads a Gemfile before gems.rb, while 2.x and later load gems.rb, so the vendored refusal of a gems.rb twin no longer claims that Bundler always loads gems.rb. The twin is still refused on every version, because vendor cannot tell which Bundler will install. The e2e premise now follows the host Bundler, which fixes the 1.17.3 leg. Refs #341 Assisted-by: Claude Code:claude-opus-5-5
The formats modules are pure by contract, and an architecture test enforces that. Move the disk and environment reads for the manifest Bundler loads into the ruby crawler, next to the BUNDLE_PATH config reader. formats::gem::manifest keeps only the classification. Refs #341, #390 Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 1, 2026 07:32
Collaborator
Author
|
BugBot review Generated by Claude Code |
A relative BUNDLE_GEMFILE was resolved against socket-patch's own working directory, so a --cwd run classified BUNDLE_GEMFILE=Gemfile as unsupported. Bundler expands the value in the directory bundle runs in, which is the project. The refusal also always said to unset the environment variable; for a .bundle/config setting it now gives the matching 'bundle config unset --local gemfile' remedy. Refs #390 Assisted-by: Claude Code:claude-opus-5-5
Collaborator
Author
|
BugBot review Generated by Claude Code |
Collaborator
Author
|
[burn-down agent] Ready for review at
Generated by Claude Code |
Union the CHANGELOG Fixed entries from both sides. Co-Authored-By: Claude <noreply@anthropic.com>
Collaborator
Author
|
bugbot run Generated by Claude Code |
Keeps both CHANGELOG entries (gem manifest fix and the Poetry venv fix from #330). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6i4cQ51yarBFnFs2b8HRx
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Oct 1, 2026
Keeps both CHANGELOG entries (gem manifest fix and the npm non-registry entry fix from #345). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6i4cQ51yarBFnFs2b8HRx
Collaborator
Author
|
bugbot run 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 a550b6f. Configure here.
Collaborator
Author
|
Burn-down agent check: ready for review at
Generated by Claude Code |
Mikola Lysenko (mikolalysenko)
enabled auto-merge (squash)
October 1, 2026 16:48
Keeps both new hosted-engine tests: the BUNDLE_GEMFILE cases from this branch and the Pipfile presence test from #425. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6i4cQ51yarBFnFs2b8HRx
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-bundler-manifest-resolution
branch
October 1, 2026 16:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #341
Fixes #390
Summary
Gem hosted and vendored modes now wire only the manifest Bundler actually loads. Before this change, both modes could edit a
Gemfilethat Bundler ignores, report success, and let VEX attest a gem that was installed unpatched.gems.rbtwin). Vendoring always wiredGemfile/Gemfile.lock, even with agems.rb/gems.lockedpair next to them. Bundler ≥ 2 loads that pair instead. Vendoring now refuses before any write, withgemfile_not_loaded. The refusal applies on every Bundler version: Bundler 1.x still reads theGemfilefirst (the 1.17.3 CI leg showed this), andvendorcan't tell which Bundler will run the install.gems.rbproject that someone then vendors keeps its hosted wiring. Without the early check it would be left unpatched in both modes.setupignoreBUNDLE_GEMFILEfrom.bundle/config, so they wireGemfilewhile bundler loads the configured manifest unpatched (VEX andsetup --checkstill pass) #390 (BUNDLE_GEMFILE). Set in the environment or in.bundle/config(bundle config set --local gemfile Gemfile.next, the dual-boot layout), it picks Bundler's manifest. Nothing in socket-patch read it.BUNDLE_GEMFILEthat names the project's ownGemfileorgems.rbselects that pair, even when the other spelling is present. Any other value (for exampleGemfile.next, or a file in another directory) redirects no gem and reportsredirect_gem_bundle_gemfile_unsupported. The in-run VEX then attests nothing.gemfile_not_loaded, as above.bundle config unset --local gemfile.setuphalf of Gem hosted redirect andsetupignoreBUNDLE_GEMFILEfrom.bundle/config, so they wireGemfilewhile bundler loads the configured manifest unpatched (VEX andsetup --checkstill pass) #390 no longer applies: v5 prerelease: scan → vex → vendor workflow, hosted by default #277 removedsetup.Root cause
Nothing answered "which manifest does Bundler load?" in one place. The hosted rewriter chose
gems.rborGemfileby filename alone, and the vendored backend hardcodedGemfile. Neither readBUNDLE_GEMFILE. Both issues come from that one missing decision, so one shared resolver fixes both:formats::gem::manifest::classifyholds the logic, with no I/O (theformatsarchitecture test enforces this).crawlers::ruby_crawler::bundler_loaded_manifestdoes the environment and app-config reads, next to the existingBUNDLE_PATHconfig reader.It follows Bundler's order:
BUNDLE_GEMFILEfrom the environment.BUNDLE_GEMFILE:in$BUNDLE_APP_CONFIG/config, else in.bundle/config.A relative value from either source is read against the project root, because that is where
bundleruns. The user-level~/.bundle/configis not consulted; the module docs note this as a known limit.A configured manifest other than
Gemfile/gems.rbis refused rather than supported. Hosted rollback, VEX discovery and the lock readers only know the two default lock names (BUNDLER_LOCKS). So a redirect written intoGemfile.next.lockcould never be found again to roll back or attest.Test evidence
gems.rbtwin is refused before any writevendor::gem::tests::gems_rb_twin_is_refused_before_any_writee2e_vendor_gem_build::gem_vendor_refuses_a_gems_rb_twingems.rbpin:get --mode vendoredrefuses before the takeover reverts ite2e_redirect_gem_build::gem_hosted_gems_rb_pin_survives_a_refused_vendored_takeover.bundle/configBUNDLE_GEMFILE: Gemfile.nextrefusedvendor::gem::tests::bundle_gemfile_naming_another_manifest_is_refusede2e_vendor_gem_build::gem_vendor_refuses_a_bundle_gemfile_dual_bootGemfile.nextredirects nothing and warnshosted::engine::tests::bundle_gemfile_naming_another_manifest_redirects_nothingBUNDLE_GEMFILE: Gemfilebesidegems.rbedits the Gemfile pairhosted::engine::tests::bundle_gemfile_naming_the_gemfile_redirects_it_over_gems_rbscan --mode hosted --vex: nothing redirected or attested, files byte-untouchede2e_redirect_gem_build::gem_hosted_bundle_gemfile_dual_boot_redirects_nothingredirected: 1,vex.statements: 1)--cwdformats::gem::manifest::tests::env_wins_over_config_and_is_anchored_at_the_project_root.bundle/config)formats::gem::manifest::tests::unsupported_detail_names_the_knob_that_set_itformats::gem::manifest::tests::*,crawlers::ruby_crawler::tests::loaded_manifest_reads_the_app_config_fileBUNDLE_GEMFILE: hosted still prefersgems.rbhosted::engine::tests::default_discovery_still_prefers_gems_rbLocal runs on ffbe645, with the gem suites re-run on 522febb (Ruby 3.3.6, Bundler 4.0.17):
e2e_redirect_gem_build -- --ignored: 8 passed.e2e_vendor_gem_build -- --ignored: 5 passed. These are the full gem capstones, including the existing ones.cargo test -p socket-patch-core --lib gem: 254 passed.cargo test --workspace --all-features --no-fail-fast(ffbe645): 9,340 passed, 12 failed. All 12 are write-failure or permission tests that fail only because the sandbox runs as root (*_write_failure_*,*unremovable*,relax_loop_must_not_traverse_symlinked_root), the same set reported on the other agent PRs. None are in gem code, and CI runs them green.cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt: the touched files are formatted.mainitself isn't fmt-clean, and CI runs no fmt gate.node --test npm/socket-patch/bin/socket-patch.test.mjs: passed. The npm, PyPI and gem wrappers only dispatch, so they need no change.Docs:
docs/ecosystems.md(RubyGems row),CLI_CONTRACT.md(new additive warning code), and aFixedentry in the CHANGELOG.🤖 Generated with Claude Code
Note
Medium Risk
Changes which Ruby manifest/lock files are mutated and when VEX attests gems; incorrect behavior previously reported patched state while Bundler installed upstream bytes.
Overview
Gem hosted and vendored modes now target only the manifest Bundler actually loads, fixing false success and VEX attestation when an ignored
Gemfilewas edited (#341, #390).A shared resolver (
formats::gem::manifest::classify+bundler_loaded_manifest) mirrors Bundler’s order:BUNDLE_GEMFILEfrom the environment, then.bundle/config, then default discovery (gems.rbbeforeGemfileon Bundler ≥ 2). Hostedscannarrows redirect candidates to that pair; other configured manifests (e.g.Gemfile.next) redirect nothing and emitredirect_gem_bundle_gemfile_unsupportedinstead ofredirect_gem_no_gemfile. Vendoredvendor/ takeover refuses withgemfile_not_loadedbefore any write (and before reverting a live hosted pin), includinggems.rbtwins and dual-boot layouts.Docs (
CLI_CONTRACT,ecosystems.md, CHANGELOG) and e2e/unit tests cover dual-boot hosted--vex, refused vendored takeover on hostedgems.rb, and real-Bundler vendor refusal paths.Reviewed by Cursor Bugbot for commit a550b6f. Configure here.
Generated by Claude Code