Skip to content

Commit 6fa7975

Browse files
masak1yuclaude
andcommitted
Prepend project bin/ to PATH in bundle exec for load-relative Ruby
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: #8441 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3e3addb commit 6fa7975

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

bundler/lib/bundler/shared_helpers.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ def set_path
342342
validate_bundle_path
343343
paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR)
344344
paths.unshift "#{Bundler.bundle_path}/bin"
345+
bin_dir = Bundler.settings[:bin] || "bin"
346+
bin_path = Pathname.new(bin_dir).expand_path(Bundler.root)
347+
paths.unshift bin_path.to_s if bin_path.directory?
345348
Bundler::SharedHelpers.set_env "PATH", paths.uniq.join(File::PATH_SEPARATOR)
346349
end
347350

spec/bundler/shared_helpers_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,37 @@
343343
it_behaves_like "ENV['PATH'] gets set correctly"
344344
end
345345

346+
context "when project bin/ directory exists" do
347+
before do
348+
Dir.mkdir bundled_app(".bundle")
349+
Dir.mkdir(bundled_app("bin")) unless File.directory?(bundled_app("bin"))
350+
ENV["PATH"] = "/usr/bin"
351+
end
352+
353+
it "prepends project bin/ to ENV['PATH'] before bundle path bin" do
354+
subject.set_bundle_environment
355+
paths = ENV["PATH"].split(File::PATH_SEPARATOR)
356+
bundle_bin_index = paths.index("#{Bundler.bundle_path}/bin")
357+
project_bin_index = paths.index(bundled_app("bin").to_s)
358+
expect(project_bin_index).not_to be_nil
359+
expect(project_bin_index).to be < bundle_bin_index
360+
end
361+
end
362+
363+
context "when project bin/ directory does not exist" do
364+
before do
365+
Dir.mkdir bundled_app(".bundle")
366+
FileUtils.rm_rf(bundled_app("bin"))
367+
ENV["PATH"] = "/usr/bin"
368+
end
369+
370+
it "does not prepend project bin/ to ENV['PATH']" do
371+
subject.set_bundle_environment
372+
paths = ENV["PATH"].split(File::PATH_SEPARATOR)
373+
expect(paths).not_to include(bundled_app("bin").to_s)
374+
end
375+
end
376+
346377
context "ENV['PATH'] already contains the bundle bin path" do
347378
let(:bundle_path) { "#{Bundler.bundle_path}/bin" }
348379

0 commit comments

Comments
 (0)