Skip to content

Unquote Gem.ruby when spawning it as a separate argv element#9695

Open
hsbt wants to merge 1 commit into
masterfrom
gem-ruby-unquote-spawn-argv
Open

Unquote Gem.ruby when spawning it as a separate argv element#9695
hsbt wants to merge 1 commit into
masterfrom
gem-ruby-unquote-spawn-argv

Conversation

@hsbt

@hsbt hsbt commented Jul 15, 2026

Copy link
Copy Markdown
Member

Gem.ruby wraps the interpreter path in double quotes when it contains whitespace, which is a historical convention for interpolating it into single-string shell commands. Four call sites pass it as a separate argv element to a non-shell spawn, where the embedded quotes become part of the file name and the spawn fails with exit 127 / Errno::ENOENT. Under an installation prefix containing a space (for example C:\Program Files), this breaks gem update --system in Gem::Commands::UpdateCommand#install_rubygems, mkrf extension builds in Gem::Ext::RakeBuilder.build, the attestation signing spawn in Gem::Commands::PushCommand#attest! (which then silently falls back to an unsigned push through its rescue), and the bundler version auto-switch in Bundler::SelfManager#restart_with. On Windows the auto-switch takes this path by default because $PROGRAM_NAME is an extensionless script for which File.executable? returns false.

This fixes each site by splitting Gem.ruby with Shellwords, following the existing idiom in Gem::Ext::Builder.ruby ("Gem.ruby is quoted if it contains whitespace"). Gem::Ext::RakeBuilder even had both patterns in the same method: the rake invocation on line 26 already used the shellsplit form while the mkrf spawn three lines above did not. The return value of Gem.ruby itself is unchanged since shebang and batch file generation depend on the quoted form.

Reproduction with a mswin Ruby 4.0.5 copied to V:\tmp\ruby with space\:

Gem.ruby      = "\"V:/tmp/ruby with space/bin/ruby.exe\""
RbConfig.ruby = "V:/tmp/ruby with space/bin/ruby.exe"
system(Gem.ruby, ...)      => nil  status=#<Process::Status: pid -1 exit 127>
system(RbConfig.ruby, ...) => true  status=0
system(*Shellwords.split(Gem.ruby), ...) => true  status=0

Manual end-to-end verification on Windows with the patched lib loaded under that space-path Ruby: the mkrf_conf spawn in RakeBuilder.build now runs (sentinel file written by the child proves the interpreter started), the patched system(*Shellwords.split(Gem.ruby), "--disable-gems", ...) call shape from install_rubygems succeeds, and the old call shape still returns nil confirming the original failure:

OK site2: mkrf_conf spawned successfully -> "mkrf ran under V:/tmp/ruby with space/bin/ruby.exe"
OK site1: system(*Shellwords.split(Gem.ruby), ...) succeeded
control: old system(Gem.ruby, ...) => nil (nil/false confirms the original bug)

Each site gets a unit test that stubs Gem.ruby to a quoted value and asserts the assembled command has an unquoted interpreter argv element.

A related but distinct issue remains in Bundler::SharedHelpers#set_rubyopt, which injects an unquoted -r<absolute path> into the space-separated RUBYOPT. RUBYOPT has no quoting mechanism, so that needs a different approach and will be addressed separately.

🤖 Generated with Claude Code

Gem.ruby wraps the interpreter path in double quotes when it contains
whitespace. That suits single-string shell commands but breaks the
multi-argument spawns that pass it as its own argv element. Under an
install prefix containing a space, `gem update --system`, mkrf extension
builds, `gem push` attestation, and the bundler auto-switch all fail to
spawn with exit 127. Split it with Shellwords, matching the existing
`Gem::Ext::Builder.ruby` idiom.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 15, 2026 07:42

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants