From a646b1503d5787fbbdde6122f349474b8712060b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Jul 2026 16:47:04 +0900 Subject: [PATCH 1/2] Avoid space-containing absolute path in RUBYOPT RUBYOPT is split on whitespace with no quoting mechanism, so the absolute `-r` path injected by set_rubyopt tears apart when bundler lives under an install prefix containing a space, and every child ruby under `bundle exec` dies at boot with "invalid switch in RUBYOPT". Fall back to requiring bundler/setup by feature name in that case; set_rubylib already puts our lib directory first on the child's load path, so the same bundler copy is still loaded. Co-Authored-By: Claude Opus 4.8 --- lib/bundler/shared_helpers.rb | 7 ++++++- spec/bundler/shared_helpers_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 2aa8abe0a078..2c22103e3066 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -347,7 +347,12 @@ def set_path def set_rubyopt rubyopt = [ENV["RUBYOPT"]].compact - setup_require = "-r#{File.expand_path("setup", __dir__)}" + setup_path = File.expand_path("setup", __dir__) + # RUBYOPT is split on whitespace with no quoting mechanism, so an + # absolute path containing spaces would be torn apart. Fall back to + # requiring by feature name; set_rubylib puts our lib directory first + # on the child's load path. + setup_require = /\s/.match?(setup_path) ? "-rbundler/setup" : "-r#{setup_path}" return if !rubyopt.empty? && rubyopt.first.include?(setup_require) rubyopt.unshift setup_require Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ") diff --git a/spec/bundler/shared_helpers_spec.rb b/spec/bundler/shared_helpers_spec.rb index ab89d280a224..4d880635717b 100644 --- a/spec/bundler/shared_helpers_spec.rb +++ b/spec/bundler/shared_helpers_spec.rb @@ -399,6 +399,33 @@ it_behaves_like "ENV['RUBYOPT'] gets set correctly" end + context "when bundler install path contains whitespace" do + let(:install_path) { "/opt/ruby with space/lib" } + + before do + allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup") + end + + # RUBYOPT is split on whitespace with no quoting mechanism, so an + # absolute -r path containing spaces would make every child ruby fail + # to boot with "invalid switch in RUBYOPT". + it "requires bundler/setup by feature name instead of absolute path" do + subject.set_bundle_environment + expect(ENV["RUBYOPT"].split(" ")).to start_with("-rbundler/setup") + end + + it "does not inject the space-containing path into RUBYOPT" do + subject.set_bundle_environment + expect(ENV["RUBYOPT"]).not_to include(install_path) + end + + it "is idempotent" do + subject.set_bundle_environment + subject.set_bundle_environment + expect(ENV["RUBYOPT"].split(" ").grep(%r{\A-r.*bundler/setup\z}).length).to eq(1) + end + end + context "ENV['RUBYLIB'] does not exist" do before { ENV.delete("RUBYLIB") } From 2d50d82e7b2d1c5280f320a50541ae423e838d11 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Jul 2026 18:27:38 +0900 Subject: [PATCH 2/2] Stub Gem.bin_path in whitespace install path specs The whitespace context stubs File.expand_path globally, so Gem.bin_path called from set_bundle_variables tries to open the fake path as a real gemspec stub and fails with ENOENT. Stub bin_path too, matching the existing special characters context. Co-Authored-By: Claude Opus 4.8 --- spec/bundler/shared_helpers_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/bundler/shared_helpers_spec.rb b/spec/bundler/shared_helpers_spec.rb index 4d880635717b..1619b0a14a59 100644 --- a/spec/bundler/shared_helpers_spec.rb +++ b/spec/bundler/shared_helpers_spec.rb @@ -404,6 +404,7 @@ before do allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup") + allow(Gem).to receive(:bin_path).and_return("#{install_path}/bundler/setup") end # RUBYOPT is split on whitespace with no quoting mechanism, so an