Skip to content

fix: subprocess probes inherited a relative BUNDLE_GEMFILE and broke on chdir - #282

Merged
pftg merged 1 commit into
masterfrom
fix/probe-env-under-ci
Aug 24, 2026
Merged

fix: subprocess probes inherited a relative BUNDLE_GEMFILE and broke on chdir#282
pftg merged 1 commit into
masterfrom
fix/probe-env-under-ci

Conversation

@pftg

@pftg pftg commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Master has been red for six consecutive commits on Test Ruby & Rails (3.3, rails81_gems.rb, false). This unblocks the release.

What broke

Six probe tests die with:

/tmp/d20260824-4743-risnub/gemfiles/rails81_gems.rb not found (Bundler::GemfileNotFound)
	from /opt/.../rubygems.rb:1397:in `<top (required)>'
	from <internal:gem_prelude>:2:in `<internal:gem_prelude>'

All six route through one helper, GemNameEntryPointTest.probe, which spawns a bare ruby with chdir: a tmpdir and passed no env — so the child inherited BUNDLER_SETUP. RubyGems ends rubygems.rb with:

require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)

The child therefore re-enters bundler/setup before it reaches -e, and resolves the workflow's relative BUNDLE_GEMFILE: gemfiles/rails81_gems.rb against its cwd — the tmpdir.

Why only ruby 3.3, and why never locally

Bundler >= 2.6 rewrites BUNDLE_GEMFILE to an absolute path during setup; the 2.5 that ships with ruby 3.3 leaves it alone. Measured:

ruby BUNDLE_GEMFILE after require "bundler/setup"
3.3.12 (bundler 2.5) gemfiles/rails81_gems.rb — relative, breaks
4.0.6 (bundler 2.7) /abs/path/gemfiles/rails81_gems.rb

bundle exec expands it too, which is why every local run was green. The 3.3/rails81 cell failed first and fail-fast cancelled the other 3.3 cells — they were not passing, just unreported.

This is the repo's own documented rule applied by halves: a subprocess probe of a tree it built itself must chdir out of the project and scrub the bundle. The chdir landed; the scrub did not.

The fix — class, not instances

  • PROBE_ENV in test_helper.rb is now the single child environment for probes that chdir out of the project: BUNDLER_SETUP, BUNDLE_GEMFILE, RUBYLIB, RUBYOPT. legacy_deletion_test.rb had hand-rolled three of the four (missing BUNDLER_SETUP) and now shares it.
  • Scrub vs absolutise, decided per probe. Every probe that spawns bare ruby with its own -I load paths wants no bundler at all → scrub. Every probe that chdir: PROJECT_ROOT genuinely wants the bundle, and a relative BUNDLE_GEMFILE resolves fine from there → left alone. A relative BUNDLE_GEMFILE stays a legitimate thing for a workflow to set, so .github/workflows/test.yml is unchanged.
  • NO_BUNDLE_GATE opens every probe and aborts if the bundle leaked in. Without it the scrub could silently stop working and stay green on every ruby but 3.3 — exactly how this reached master six times.
  • The gate is tested against the opposite condition: restoring BUNDLER_SETUP alone must make the same probe refuse to run.

Verification

Mutation — reverting the scrub, under a CI-shaped env (relative BUNDLE_GEMFILE), reproduces the CI trace exactly:

bundler/definition.rb:38: /var/folders/.../T/d20260824-32758-8r245p/gemfiles/rails81_gems.rb not found (Bundler::GemfileNotFound)
	from rubygems.rb:1459:in '<top (required)>'
	from <internal:gem_prelude>:2
4 runs, 4 failures

The same mutation under a plain local run — where the old code was silently green — now fails loudly:

GATE: the probe inherited the development bundle, so it proves nothing

That is the hole this closes. Restore verified byte-identical (sha256 5b6ac333… before and after).

Green both ways: rake test:unit 720 runs / 0 failures normally and with the env CI uses (CI=true + relative BUNDLE_GEMFILE); rake test:canonical 610 runs / 0 failures; standardrb lib test 161 files, no offenses.

No version.rb or CHANGELOG.md changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_014BQJX6eWzBj2UTm5zQsjEs

Summary by Sourcery

Prevent subprocess probes from inheriting the development bundle when they run from temporary or deleted project trees.

Bug Fixes:

  • Prevent subprocess probes from inheriting the development Bundler environment when running outside the project, avoiding failures caused by relative bundle paths after chdir.
  • Share consistent environment cleanup across subprocess probes, including legacy deletion checks.

Enhancements:

  • Add a subprocess guard that fails loudly when a probe accidentally runs with Bundler loaded.
  • Add coverage verifying that the guard rejects an inherited Bundler setup before executing probe code.

Tests:

  • Add regression coverage for rejecting leaked Bundler state in subprocess probes.

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation for commands run outside the project directory, preventing inherited development environment settings from affecting results.
    • Added safeguards to detect and reject improperly configured subprocess environments before execution.
  • Tests

    • Expanded automated coverage for environment isolation and legacy project-tree scenarios.
    • Updated probe guidance to ensure reliable behavior when working with removed or isolated project files.

…on chdir

Master has been red for six commits on the (3.3, rails81_gems.rb) cell:
six probe tests dying with

  /tmp/d20260824-4743-risnub/gemfiles/rails81_gems.rb not found
  (Bundler::GemfileNotFound)

All six route through GemNameEntryPointTest.probe, which spawns a bare
ruby with `chdir:` a tmpdir and passed NO env, so the child inherited
BUNDLER_SETUP. RubyGems ends rubygems.rb with

  require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)

so the child re-entered bundler/setup before reaching `-e`, and resolved
the workflow's RELATIVE `BUNDLE_GEMFILE: gemfiles/rails81_gems.rb`
against the tmpdir. Bundler >= 2.6 rewrites BUNDLE_GEMFILE to an absolute
path during setup and the 2.5 that ships with ruby 3.3 does not, which is
why only the 3.3 cells saw it -- and why it is invisible locally, where
`bundle exec` expands it too.

This is the repo's own rule applied by halves: a probe of a tree it built
itself must chdir out of the project AND scrub the bundle. The chdir
landed; the scrub did not.

Fixed as a class, not an instance:

- PROBE_ENV in test_helper.rb is now the one child environment for probes
  that chdir out of the project -- BUNDLER_SETUP, BUNDLE_GEMFILE, RUBYLIB,
  RUBYOPT. legacy_deletion_test.rb had hand-rolled three of the four
  (missing BUNDLER_SETUP) and now shares it.
- The probes that chdir INTO the project root are untouched: they
  genuinely want the bundle, and a relative BUNDLE_GEMFILE resolves fine
  from there. A relative BUNDLE_GEMFILE stays a legitimate thing for the
  workflow to set, so test.yml is unchanged.
- NO_BUNDLE_GATE opens every probe and aborts if the bundle leaked in.
  Without it the scrub could silently stop working and stay green on
  every ruby but 3.3 -- exactly how this reached master six times. Under
  a plain local run, reverting the scrub now fails loudly with
  "GATE: the probe inherited the development bundle" instead of passing.
- That gate is itself tested against the opposite condition: restoring
  BUNDLER_SETUP alone must make the same probe refuse to run.

Verified: rake test:unit green both ways -- normal, and with the env CI
uses (CI=true plus a relative BUNDLE_GEMFILE forced back after
bundler/setup, which reproduces the 3.3 bundler on any ruby). Under that
CI-shaped env the pre-fix probe reproduces the exact CI stack trace at
rubygems.rb:1459. rake test:canonical and standardrb lib test green.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 60a82b62-f6fe-4adb-86f7-8d5741b784d4

📥 Commits

Reviewing files that changed from the base of the PR and between 75cf9f9 and bf694c1.

📒 Files selected for processing (3)
  • test/test_helper.rb
  • test/unit/gem_name_entry_point_test.rb
  • test/unit/legacy_deletion_test.rb

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Subprocess probes now use a shared environment that removes Bundler and project load-path settings. The probe helper adds a no-Bundler gate. Legacy deletion probes merge their deleted-tree setting into the shared environment, with coverage for inherited Bundler setup.

Changes

Probe isolation

Layer / File(s) Summary
Shared probe environment contract
test/test_helper.rb
Defines frozen PROBE_ENV overrides for Bundler and Ruby load-path settings.
Probe execution gate and validation
test/unit/gem_name_entry_point_test.rb, test/unit/legacy_deletion_test.rb
Passes the environment to subprocess probes, rejects probes when Bundler is loaded, and applies PROBE_ENV to legacy deletion probes. Tests cover inherited BUNDLER_SETUP rejection.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to bf694

The change isolates test subprocesses from inherited bundle settings and adds regression coverage; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main fix: preventing subprocess probes from inheriting a relative BUNDLE_GEMFILE when changing directories.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/probe-env-under-ci

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pftg
pftg merged commit 50ae410 into master Aug 24, 2026
7 checks passed
@pftg
pftg deleted the fix/probe-env-under-ci branch August 24, 2026 13:30

@sourcery-ai sourcery-ai Bot left a comment

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.

Sorry @pftg, you have reached your weekly rate limit of 250000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fix subprocess probes that chdir into temporary or deleted trees by scrubbing inherited Bundler and Ruby environment variables, while adding an executable gate and regression test to ensure probes cannot silently validate the wrong environment.

File-Level Changes

Change Details Files
Centralize subprocess environment isolation for probes that run outside the project tree.
  • Define a frozen environment scrubber removing Bundler and Ruby load-path variables.
  • Reuse the scrubber in the legacy deleted-tree probe instead of maintaining a partial copy.
  • Keep probes rooted at the project directory on the inherited bundle because they intentionally exercise it.
test/test_helper.rb
test/unit/legacy_deletion_test.rb
Prevent probes from silently running under the development bundle and add regression coverage for the original failure mode.
  • Inject a child-process gate that aborts if Bundler is already loaded.
  • Apply the gate and scrubbed environment to gem-name entry-point probes.
  • Add a test that restores BUNDLER_SETUP and verifies the gate aborts before executing the probe body.
test/unit/gem_name_entry_point_test.rb

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant