Skip to content

Let the gem and bundle cooldown settings cover each other - #9852

Open
hsbt wants to merge 22 commits into
masterfrom
cooldown-cross-reference
Open

Let the gem and bundle cooldown settings cover each other#9852
hsbt wants to merge 22 commits into
masterfrom
cooldown-cross-reference

Conversation

@hsbt

@hsbt hsbt commented Sep 3, 2026

Copy link
Copy Markdown
Member

Setting a cooldown for one of the two tools left the other unprotected. gem read only the :cooldown: gemrc setting and bundler only its own, so a BUNDLE_COOLDOWN in a shell profile did nothing for gem install. Both now read both settings and the longer one applies. gem --cooldown has not shipped in a release yet, so no released behavior changes.

A configured 0 takes part like any other value rather than reading as unset, which keeps --cooldown 0 the way to bypass a cooldown the other tool configures. The flag wins outright, ahead of both settings and of the Gemfile per-source value.

$ cat ~/.gemrc
:cooldown: 7
$ bundle install                       # cooldown 7 applies, read from the gemrc
$ BUNDLE_COOLDOWN=14 gem install foo   # 14 wins over the gemrc 7
$ gem install foo --cooldown 0         # bypasses both

Gem::CooldownSettings holds the value rules both tools now share. Gem::BundlerSettings reads Bundler's config the way Bundler::Settings does, and folding Gem::BundlerVersionFinder onto it fixes an ignored BUNDLE_APP_CONFIG and a missing path separator that kept BUNDLE_VERSION in ~/.bundle/config from ever being read.

Generated with Claude Code

hsbt and others added 22 commits September 3, 2026 17:28
Gem::BundlerVersionFinder resolved Bundler's config files itself, and got
two of the paths wrong: it ignored BUNDLE_APP_CONFIG, and it concatenated
BUNDLE_USER_HOME and the home directory with the file name instead of
joining them, so BUNDLE_VERSION set in ~/.bundle/config was never found.
It also matched BUNDLE_VERSION with a regex of its own rather than
parsing the file.

Gem::BundlerSettings now does the resolution for every RubyGems caller
that has to agree with something configured for Bundler, following
Bundler::Settings for both the paths and the precedence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each of the five commands that take the flag validated it and set it as
a command option itself, and install read it under a string key while
the rest used a symbol.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Setting a cooldown for one of the two tools left the other unprotected,
since `gem` only read the `:cooldown:` gemrc setting and bundler only
read its own. They now read both, and the longer of the two applies, so
either setting alone covers `gem install` and `bundle install` alike.

A configured 0 takes part like any other value rather than reading as
unset, which keeps `--cooldown 0` the way to bypass a cooldown the other
tool configures: the flag wins outright, ahead of both settings and of
the Gemfile per-source value.

Gem::CooldownSettings holds the value rules both tools now share, so a
value that cannot be read as a number of days warns and takes no part in
the resolution on either side. On the bundler side that value used to
read as 0 and suppress a per-source `cooldown:` in the Gemfile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The outdated command reads Bundler's application config now, and that
file is found by walking up from the working directory, so its tests
have to close off an inherited BUNDLE_GEMFILE the way the other cooldown
tests already do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gem::YAMLSerializer signals a bad document with Psych::DisallowedClass,
Psych::BadAlias and Psych::SyntaxError, all of them RuntimeError, so the
rescue let them past and a stray `*glob` value in a project's
`.bundle/config` aborted `gem install` with a backtrace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A config file starting with a list gave `Array#[]` a String key, so
`gem install` and bundler activation raised TypeError where the regex
reader they replaced had simply found nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dir.pwd raises Errno::EACCES when an ancestor denies search to the
current uid, and cooldown resolution puts this call on the default
`gem install` path where the deleted-directory rescue alone is not
enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Integer() reads a radix prefix, so routing the Bundler setting through it
turned a shipped `BUNDLE_COOLDOWN=010` from ten days into eight and
`08` into no cooldown at all, neither of them announced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three other files the Bundler gem ships out of lib/rubygems carry
this guard, and a load order that puts the gem's copy first would
otherwise redefine the module and warn under -w.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Checking it when the command starts built Gem::ConfigFile for every
bundle command, and the first require of a default gem through RubyGems
costs around 30ms that a command resolving nothing has no use for.
Measured on `bundle install --local`: 0.46s before, 0.43s after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cooldown resolution reads Bundler's settings now, so a developer with one
configured in their shell saw it applied to the fixtures: four install
command tests and two update command tests failed under
BUNDLE_COOLDOWN=60.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The teardown wrote nil rather than the value it replaced, leaving
Gem.configuration, which the suite shares across a process, holding
something it never started with.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each tool reports the other's setting now, so the same complaint could
come from the gemrc file or from Bundler's configuration with nothing to
tell them apart, and the first one silenced the second.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A gemrc cooldown raises the per-source value like it raises any other
layer, which the page documents a few paragraphs down while still
offering `cooldown: 0` as a permanent exemption above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only the option disables the cooldown outright. A 0 in either config
file is one value among two now, and loses to a period configured in the
other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gemrc value takes part in resolving the cooldown but is not one of
the config layers, so `bundle config get cooldown` answered that nothing
was configured while the cooldown was in force.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Its siblings serializer_class and credential_store_class are private,
and nothing outside the class reaches for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It claimed to follow Bundler::Settings, which is true of the layer order
and of nothing else: URI-shaped keys, renamed keys and the defaults layer
are all absent, and the application config file is only found through a
Gemfile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gem::ConfigFile keeps the parsed file and the accessor in separate
instance variables, so an embedder assigning the accessor gets a value
this reader does not see.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec support keeps its own list of the lib/rubygems files that travel
with Bundler, so building the gem under ruby-core rejected a gemspec
naming a file that had never been copied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gem::ConfigFile#[] there looks the key up as a String, which never
matches the Symbol a `:cooldown:` entry is stored under, so Bundler saw
no gemrc cooldown at all on the RubyGems that Ruby 3.2 ships. Scanning
the loaded settings finds it under either key.

Co-Authored-By: Claude Opus 5 <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