Skip to content

Prepend project bin/ to PATH in bundle exec for load-relative Ruby#9478

Open
masak1yu wants to merge 2 commits intoruby:masterfrom
masak1yu:fix/bundle-exec-prepend-bin-path-for-load-relative-ruby
Open

Prepend project bin/ to PATH in bundle exec for load-relative Ruby#9478
masak1yu wants to merge 2 commits intoruby:masterfrom
masak1yu:fix/bundle-exec-prepend-bin-path-for-load-relative-ruby

Conversation

@masak1yu
Copy link
Copy Markdown

Summary

When Ruby is compiled with --enable-load-relative (as done by mise), RubyGems generates binstubs with a #!/bin/sh shell wrapper that resolves ruby relative to the binstub directory:

bindir="${0%/*}"
exec "$bindir/ruby" "-x" "$0" "$@"

When BUNDLE_PATH is set to vendor/bundle, set_path prepends vendor/bundle/ruby/X.Y.Z/bin to PATH. bundle exec then finds the vendor binstub first via Bundler.which, but since its shebang is #!/bin/sh (not #!/usr/bin/env ruby), ruby_shebang? returns false and Bundler falls back to kernel_exec. The OS executes the shell wrapper, which looks for $bindir/ruby in the vendor bin directory — where no ruby binary exists — and fails:

vendor/bundle/ruby/3.3.0/bin/rspec: line 6: vendor/bundle/ruby/3.3.0/bin/ruby: No such file or directory

Fix

This PR prepends the project bin/ directory (where Bundler generates binstubs with #!/usr/bin/env ruby via env_shebang: true) to PATH before the vendor bin path in SharedHelpers#set_path. This ensures Bundler.which finds the correctly-shimmed project binstub first, allowing ruby_shebang? to return true and kernel_load to execute the command in-process.

The project bin/ is only added when the directory actually exists, avoiding side effects for projects without binstubs.

Root Cause Analysis

Three factors combine to produce this bug:

  1. --enable-load-relative Ruby (mise builds): LIBRUBY_RELATIVE=yes triggers bash_prolog_script in Gem::Installer#shebang, wrapping every vendor binstub in a #!/bin/sh prologue
  2. set_path only prepends vendor bin: vendor/bundle/ruby/X.Y.Z/bin is added to PATH but bin/ is not, so the broken vendor binstub is found first
  3. ruby_shebang? doesn't match #!/bin/sh: The shell wrapper falls through to kernel_exec, which executes it as a shell script that fails to find $bindir/ruby

Related Issues

Test Plan

  • Added spec: project bin/ directory is prepended to PATH before vendor bundle bin when it exists
  • Added spec: project bin/ is not added to PATH when the directory doesn't exist
  • Verified manually: bundle exec rspec works on a project with BUNDLE_PATH=vendor/bundle and mise Ruby 3.3.10

masak1yu and others added 2 commits April 13, 2026 21:10
When Ruby is compiled with `--enable-load-relative` (as done by mise),
RubyGems generates binstubs with a `#!/bin/sh` wrapper that resolves
`ruby` relative to the binstub directory (`exec "$bindir/ruby"`).

When `BUNDLE_PATH` is set to `vendor/bundle`, `set_path` prepends
`vendor/bundle/ruby/X.Y.Z/bin` to PATH. `bundle exec` then finds the
vendor binstub first, but since its shebang is `#!/bin/sh` (not
`#!/usr/bin/env ruby`), `ruby_shebang?` returns false and Bundler
falls back to `kernel_exec`. The OS executes the shell wrapper, which
looks for `$bindir/ruby` in the vendor bin directory — where no `ruby`
binary exists — and fails.

This fix prepends the project `bin/` directory (where Bundler generates
binstubs with `#!/usr/bin/env ruby`) to PATH before the vendor bin
path, so `Bundler.which` finds the correctly-shimmed binstub first.
The project bin/ is only added when the directory actually exists.

Fixes an interaction between load-relative Ruby builds (mise, some
asdf configurations) and `BUNDLE_PATH=vendor/bundle`.

Related: ruby#8441

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lves correctly

The test was failing because BUNDLE_GEMFILE was set to a relative "Gemfile"
path (inherited from the parent describe block), causing Bundler.root to
resolve to the process's current working directory instead of bundled_app.
This meant bin_path.directory? could not find the test's bin/ directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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