Skip to content

Perform the install triggered by auto_install in a subprocess - #9758

Open
meganemura wants to merge 1 commit into
ruby:masterfrom
meganemura:auto-install-in-subprocess
Open

Perform the install triggered by auto_install in a subprocess#9758
meganemura wants to merge 1 commit into
ruby:masterfrom
meganemura:auto-install-in-subprocess

Conversation

@meganemura

@meganemura meganemura commented Aug 4, 2026

Copy link
Copy Markdown

What was the end-user or developer problem that led to this PR?

With auto_install enabled, the first require "bundler/setup" (or bundle exec) after gems go missing installs them and then fails:

Automatically installing missing gems.
...
Bundle complete! 2 Gemfile dependencies, 2 gems now installed.
/path/to/lib/bundler/runtime.rb:328:in 'Bundler::Runtime#check_for_activated_spec!': You have already activated openssl 3.2.0, but your Gemfile requires openssl 3.2.1. Since openssl is a default gem, you can either remove your dependency on it or try updating to a newer version of bundler that supports openssl as a default gem. (Gem::LoadError)

The install itself succeeded, so running the same command again works, which makes it look unreproducible. Both suggestions in the error message are dead ends: openssl is usually a transitive dependency that can't be dropped from the Gemfile, and the bundler in use is already current.

Reproduction, on a Ruby whose default openssl differs from the pinned version:

WORK="$(mktemp -d)"; cd "$WORK"; mkdir gems
export GEM_HOME="$WORK/gems" GEM_PATH="$WORK/gems"
unset BUNDLE_GEMFILE RUBYOPT

cat > Gemfile <<'EOF'
source "https://rubygems.org"
gem "openssl", "3.2.1"   # any released version != this Ruby's default openssl
gem "rake"               # any gem at all, it only has to be missing
EOF

bundle lock
BUNDLE_AUTO_INSTALL=true ruby -e 'require "bundler/setup"; puts "OK"'   # => Gem::LoadError

Only auto_install is affected, since it is what chains an install and a Bundler.setup in one process. bundle exec <binstub> reaches the same path; bundle exec ruby ... does not, because ruby is Kernel.execed and activation is reset.

What is your fix for the problem, implemented in this PR?

Installing talks to the gem source over HTTPS, which requires openssl. Bundler.setup has not run at that point, so no Gemfile requirement is in effect and RubyGems activates the newest installed version, which is the default gem while the locked one is still missing. Activation is one version per process and can't be undone — reset! only drops Bundler's Definition, not RubyGems' activated specs — so the Bundler.setup that follows activates the lockfile's version and raises. Bundler normally avoids this class of conflict by vendoring what it needs, but openssl is a C extension and can't be vendored.

So run the install in a forked child, the way bundler/inline already does for the same reason, and keep installing in process on platforms without fork, also as in bundler/inline.

Unlike bundler/inline, this does not call Gem.load_yaml before forking: on Ruby 3.2 that requires psych unconditionally, activating the default psych in the parent, which is the very conflict this PR removes. bundler/inline can afford the pre-activation because it recovers by re-resolving with the activated version; here Bundler.setup must activate what the lockfile says. The NameError that call is meant to prevent did not occur, because Bundler.setup has already loaded the real Psych by the time YAML is loaded.

Tests

spec/bundler/bundler_spec.rb covers the install running in a subprocess and a failed install exiting with the child's status code. spec/runtime/setup_gems_spec.rb covers an automatic install of a bundle that locks a default gem to another version; it uses a psych 999 fixture rather than openssl because the suite stubs HTTPS, the same stand-in spec/runtime/inline_spec.rb already uses.

Make sure the following tasks are checked

With `auto_install`, Bundler installs missing gems in the same process
that then runs `Bundler.setup`. Installing requires `openssl` for HTTPS
remotes, and no Gemfile requirement is in effect yet, so RubyGems
activates the default gem while the locked one is still missing.
Activation can't be undone, so the `Bundler.setup` that follows raises a
`Gem::LoadError` when the lockfile pins `openssl` to another version.

Do the install in a forked child, like `bundler/inline` already does for
the same reason. Platforms without `fork` keep installing in process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@meganemura
meganemura force-pushed the auto-install-in-subprocess branch from 684bb30 to d3d2b3d Compare August 8, 2026 04:51
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