Skip to content

Fix Bundler path tier read two ways (#1931) - #1943

Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-bundler-path-tier-resolver
Nov 2, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-bundler-path-tier-resolver

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

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

Fixes #1931

What changes for users

In a Ruby project whose Bundler config sets disable_shared_gems to a false value ("false", "no", "0" or empty) in .bundle/config, the environment or the global config, Bundler installs and loads gems from the system gem homes, exactly as it does for path.system: true. socket-patch now treats it the same way:

  • A leftover vendor/bundle is no longer crawled and no longer switches off the gem env homes, so apply patches the copy Bundler actually loads.
  • A path recorded in the same config file as that flag is ignored too (local or global).

Before this change, apply could report success after patching a copy Bundler never loads.

Root cause

Bundler's Settings#path tier rule (the first of local, env and global that sets path, path.system or disable_shared_gems decides, and a truthy path.system or a falsy disable_shared_gems means system gems) was written twice in crawlers/ruby_crawler.rs. bundler_sets_explicit_path followed Bundler. Store discovery's copy (bundler_path_tier + config_path_tier) read system from path.system only and never saw the env BUNDLE_DISABLE_SHARED_GEMS.

The fix

  • BundlerPathSettings::decides_path / uses_system_gems and BundlerPathTiers::deciding form the one tier resolver. Store discovery, the global-path shadowing check and bundler_sets_explicit_path all read through it.

  • Removed: BundlerPathTier, config_path_tier, bundler_path_tier, app_config_sets_path, global_path_config_unless_env_path_settings and ambient_path_system_env. The env tier's flags reach discovery as BundlerEnvPathFlags instead of pre-filtering the global config file.

  • parse_bundle_config_path drops a recorded path when the same file sets a falsy disable_shared_gems, as it already did for path.system: true.

  • Side effects, both consistent with Bundler: an env BUNDLE_PATH__SYSTEM or BUNDLE_DISABLE_SHARED_GEMS no longer hides the whole global config file from discovery (only its path is shadowed), so a global BUNDLE_GEMFILE is still honored; and verification_only_gem_paths now passes the ambient env flags into discovery.

Tests (red → green)

Issue case Test Red without fix
#1931 local / global disable_shared_gems falsy + leftover vendor/bundle falsy_disable_shared_gems_drops_leftover_vendor_bundle yes (local false: [".../vendor/bundle/ruby/3.3.0/gems"])
#1931 env BUNDLE_DISABLE_SHARED_GEMS=false env_falsy_disable_shared_gems_drops_leftover_vendor_bundle yes (mutation: resolver without the disable_shared_gems arm)
same-tier recorded path falsy_disable_shared_gems_drops_same_tier_path yes (local: [".../vendor/x/ruby/3.3.0/gems"])
controls (truthy flag, shadowed falsy flag) truthy_or_shadowed_disable_shared_gems_keeps_vendor_bundle n/a (passes before and after)
env flags shadow a global path (replaces the removed helper's unit test) env_path_flags_shadow_the_global_config_path n/a

The mutation check (dropping the disable_shared_gems arm from uses_system_gems) also fails the existing bundler_sets_explicit_path_follows_settings_tiers, which shows discovery and the stale-install/vex guard now share one rule.

Local runs:

  • cargo fmt --all -- --check: ok
  • cargo clippy --workspace --all-features -- -D warnings: ok
  • cargo test --workspace --all-features --no-fail-fast: 236 targets pass. Four targets fail only in this sandbox and none touch Ruby code: socket-patch-core --lib (4 tests), in_process_redirect (3), repair (2), e2e_bun_lockb (2). The first three inject write failures with read-only dirs, which the container's root user ignores. e2e_bun_lockb needs the live patch API (see Fix bun.lockb nightly e2e reaching the live blob API #1905) and depends on the local bun 1.4.4 build. Draft CI on ba9a49b was green.
  • Ruby crawler unit tests: 112 pass. crawler_ruby_e2e: 30 pass. e2e_redirect_gem_stale_install: 42 pass. e2e_redirect_gem_build -- --ignored (Bundler 4.0.18): 37 pass. e2e_vendor_gem_build -- --ignored: 11 pass.
  • No changes to the npm/pypi/gem wrappers: this is crawler logic only.

Code review findings not fixed here

/code-review high ran on the diff. The real findings are fixed in fe20764 (same-tier path, single resolver call, stale comments). Three were left as they are:

Follow-ups


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A project whose Bundler config sets disable_shared_gems to a false
value ("false", "no", "0" or empty, in .bundle/config, the
environment or the global config) installs and loads its gems from
the system gem homes, just like path.system true. Store discovery
only looked at path.system, so it still crawled a leftover
vendor/bundle, dropped the gem env homes, and apply patched a copy
Bundler never loads.

The tier rule ("the first tier that sets path, path.system or
disable_shared_gems decides") now has one resolver,
BundlerPathTiers::deciding, read by store discovery, the global-path
shadowing check and bundler_sets_explicit_path. The second struct
(BundlerPathTier), config_path_tier, app_config_sets_path and the
env pre-filter on the global config are gone.

Fixes #1931

Assisted-by: Claude Code:claude-opus-5-5
A Bundler config that records a path and, in the same file, sets
disable_shared_gems to a false value sends Bundler to the system gem
homes, so the recorded path (local or global) is not where it loads
gems. Discovery still crawled it and apply could patch that unused
copy; it now drops the path the same way path.system true does.

bundler_sets_explicit_path also picks its deciding tier through
BundlerPathTiers::deciding_of, the same resolver discovery uses, and
the discovery comments now name both system-gem flags.

Refs #1931

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review November 1, 2026 17:16
@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 fe20764. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] hosted-e2e (and the ci-ok gate that depends on it) is red on fe20764. It failed again on one re-run. The cause is outside this PR: the suite runs against live production (patch.socket.dev), and production changed after main's last green run at 05:42 UTC today. This PR only changes how the Ruby crawler finds Bundler install roots. The hosted redirect path these tests check doesn't touch that code.

  • bun_hosted_install_proof: GET https://patch.socket.dev/patch/npm/minimist/1.2.2/…/minimist-1.2.2.tgz returns 403. That is a production grant or data problem, and no change in this repo can fix it.
  • gem_bundler_hosted_install_proof: production now also serves a patch for actionpack 6.0.3 (grant 78b877c4…, patch f86d5caa-9eb0-4984-9ec7-87d5b0c83ed6). The fixture pulls actionpack in transitively, so the rewritten Gemfile now gets a Socket source block for actionpack before the activestorage one. wired_gem_registry_uuid (tests/e2e_hosted_production.rs:714) reads the first block, so it reports the actionpack UUID and fails the GEM_UUIDS any-of check. The test-side fix is to match the block that wraps gem "activestorage". That belongs in its own ci-janitor / agent/ci-* PR. No such fix exists yet.

All other checks are green, and Bugbot's review of fe20764 found no issues. A maintainer needs to look at the bun 403 on the production side.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Update: hosted-e2e passed on a later re-run (19:12 UTC), so every check on fe20764 is now green. The production-side problems from my earlier comment cleared on their own. The PR is waiting on human review.


Generated by Claude Code

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

Copy link
Copy Markdown
Collaborator Author

[burn-down agent] Ready for review at fe20764.

  • CI: 82/82 check runs on fe20764 green (success/skipped/neutral); no main-wide failures.
  • Bugbot: reviewed fe20764, no findings; no open review threads.
  • Mergeable, no conflicts with main; no CHANGELOG.md change.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: Fixes #1931. Store discovery had its own copy of Bundler's Settings#path tier rule. That copy counted only a truthy path.system as "system gems" and ignored disable_shared_gems. So BUNDLE_DISABLE_SHARED_GEMS=false next to a leftover vendor/bundle made apply patch a copy Bundler never loads. The PR replaces the copies with one resolver (BundlerPathSettings / BundlerPathTiers::deciding), used by discovery, the global-path shadow check and bundler_sets_explicit_path.

Risk: low. The change is one file of crawler logic. I checked it line by line against Bundler 4.0.18's settings.rb:

  • Tiers are local/env/global; the first tier that sets path, path.system or disable_shared_gems decides.
  • system = path_system || disable_shared_gems == false.
  • Deployment applies only when no tier decides.
  • to_bool matches bundler_truthy.

Look here:

  • ruby_crawler.rs:460-472:`` discovery builds the tiers and calls .deciding().
  • ruby_crawler.rs:518-521:``` the global path now applies only when the global tier decides.
  • ruby_crawler.rs:1317-1333:```` decides_path / `uses_system_gems`, the core rule.
  • ruby_crawler.rs:1381-1449:````` deciding_of and the rewritten `bundler_sets_explicit_path`.
  • ruby_crawler.rs:2424-2440:`````` the same-file path drop when disable_shared_gems is falsy (Bundler rejects that combination anyway).

Verified:

Changes I made: None.

Open questions: None that block merging. Small side effects the description doesn't mention, all consistent with Bundler:

  • An env BUNDLE_PATH__SYSTEM or BUNDLE_DISABLE_SHARED_GEMS no longer drops the whole global config, so a global BUNDLE_GEMFILE is still honored.
  • verification_only_gem_paths now passes the ambient env flags.

The env-BUNDLE_PATH probe that already exists on main, for a local tier that decides without a path, is deferred to #1935 as the description says.

Auto-merge is armed: approving sends this straight to the merge queue.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[agent] Reviewed fe207642712edbc2c7b0c810a718f22146afaba3. Checked Bundler local/env/global path tier precedence and system/shared-gem flags. No major correctness issues found. Validation: source and regression-test review plus the available exact-head CI checks; no local test pass is claimed.

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 3210900 Nov 2, 2026
166 of 170 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-bundler-path-tier-resolver branch November 2, 2026 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bundler's path tier rule is written twice in ruby_crawler, and store discovery ignores BUNDLE_DISABLE_SHARED_GEMS: "false"

3 participants