
RubyGems 4.1: The Resolver Swap Is the Story
Quick answer: RubyGems and Bundler 4.1.0.beta1 shipped on 9 September 2026. The post-quantum signing headline (ML-DSA for signed gems) lands in a workflow that is opt-in and, by our reading of the ecosystem, rarely enabled. The change that touches every Ruby project is the replacement of the Molinillo dependency resolver with PubGrub, which is on by default. Test that first, and test it for error-message and CI-script breakage rather than for lockfile churn — the evidence from the migration work says lockfiles held.
Release announcements sort features by how impressive they sound. "Post-quantum cryptographic signatures for gems" sounds considerably more impressive than "new dependency resolver." For your CI pipeline, the ranking is exactly the other way round.
Here is the 4.1.0.beta1 feature list from the release notes, sorted by how many projects it actually reaches.
| Change | Default? | Who it reaches |
|---|---|---|
| PubGrub replaces Molinillo | Yes | Every project that resolves dependencies |
| Content-addressable gems | Yes | Anyone with a warm gem cache |
| Breaking removals (see below) | Yes | Native-extension gems, plugin authors |
--cooldown on install/update/outdated | Flag | Teams that opt in |
| OS credential store | Opt-in | Developers on workstations |
| ML-DSA post-quantum gem signing | In the signing workflow | The small set of signed-gem publishers |
Why the resolver was replaced
The migration pull request is unusually blunt about the motivation: "Our current resolve engine has performance issues where sometimes it will hang during resolution due to an excessive number of resolution steps. In addition to that, its resolution error messages tend to be confusing and too verbose, not highlighting the real resolution culprits."
Those are two different problems. The hang is a pathological-case complexity problem in backtracking. The error messages are a design problem: Molinillo reports the search it performed, PubGrub reports the incompatibilities it derived. Anyone who has ever stared at a forty-line "Bundler could not find compatible versions" wall and still not known which gem to pin knows the difference matters.
PubGrub is a version-solving algorithm originally developed for Dart's pub, and its selling point is exactly that derivation trail. A reviewer on the migration thread put it plainly: "when it can't find a solution pub_grub has so much better output describing the incompatibilities."
What the migration evidence says about risk
The obvious fear with a resolver swap is that a different algorithm picks different versions and your lockfile churns across the whole dependency tree. The testing reported on the migration thread points the other way: multiple testers confirmed lockfiles remained unchanged after the switch, and performance was reported as comparable — one organisation described bundle operations running "at about the same speed (maybe a few percent faster)."
Take that as encouraging, not as a guarantee. Two caveats that the release notes do not address and you should not assume away:
First, unchanged lockfiles are a property of the graphs that were tested. A resolver is free to return any solution satisfying the constraints. Where multiple valid solutions exist — loose upper bounds, conflicting transitive peers, platform-specific variants — the two engines can legitimately disagree. Beta testing on mainstream Rails applications does not sample the awkward tail.
Second, better error messages are still different error messages. If anything in your pipeline greps Bundler's stderr — a retry wrapper, a Slack notifier, a dependency-bot heuristic — that string matching is what breaks, not your lockfile. The thread even shows the wording being debated during review, with "forbidden" criticised as misleading and "cannot be used" proposed instead. Text that is still being reworded in review is text you should not be parsing.
This is the same failure shape as the recent npm audit endpoint retirement, where the breakage landed in tooling that depended on a response shape rather than in anyone's dependency graph.
Content-addressable gems and the shared cache
The second default-on change is content-addressable gem support, alongside a global gem cache shared between RubyGems and Bundler. Addressing a gem by the hash of its contents rather than by name and version means you can tell whether two downloads of foo-1.2.3 are byte-identical, which is the foundation every reproducible-build story is built on.
The shared cache is the immediate practical win: RubyGems and Bundler stop keeping duplicate copies of the same artifact. On a CI runner where the cache is restored on every job, halving the duplicated payload is real minutes. Bundler also gains a prune setting to "drop rebuildable install artifacts," and keep_outdated_cache replaces the deprecated no_prune — a rename worth grepping your .bundle/config for.
--cooldown lands in Ruby too
4.1.0.beta1 adds --cooldown to gem install, gem update and gem outdated, and the notes say cooldown settings now "cover each other" between the gem and bundle commands. The premise is that a freshly published version is the riskiest version, so you decline to install anything younger than a chosen age.
Ruby is arriving at a party the JavaScript registries got to first, and the same caveat applies: a cooldown delays adoption, it does not inspect anything. It also does nothing about lifecycle and extension code that runs at install time. We went through the cross-ecosystem detail in install cooldowns in npm, pnpm, Yarn and Bun, and the conclusions transfer directly.
Relatedly, gem install gains --no-build-extension and --no-install-plugin. Those are the flags that actually reduce what executes during an install, and they pair with the cooldown rather than duplicating it.
The breaking changes, and who trips over them
Five removals ship as defaults. RubyGems stops installing native extension build logs. Gem::BasicSpecification#datadir is removed. Building a gem that references itself now raises an error instead of producing something strange. The DEFAULT_INSTALL_EXTENSION_IN_LIB behaviour is reverted, with a warning for require_relative alongside a C extension. The deprecated Gem::List is gone. Separately, bundle env no longer checks external tool versions, and the Ruby DSL ignores the patchlevel keyword argument.
None of those will break an average application. All of them can break a gem with a native extension or a RubyGems plugin, which is the population that should be testing the beta rather than waiting for the final. If you maintain something in that category, the open-source listings are a reasonable place to check whether your dependencies have caught up.
Signing: correct, and not the thing to plan around
ML-DSA support in the signed-gems workflow is the right move at the right time — migrating signature algorithms takes years, and starting before you need to is the only way it ever happens. But it lands in a workflow that most publishers never enabled. RubyGems' certificate-based signing requires developer-held key management, which is the usual reason opt-in signing schemes stay niche.
The trust mechanism that actually spread across registries in 2026 is build provenance: attestations tied to a CI identity rather than to a developer-held key. That is the model behind npm's trusted publishing and the stage-only token work, and it is the direction registries keep moving. Post-quantum gem signatures are insurance on a road fewer people drive.
What to do with a beta
It is a beta, so the answer is not "upgrade production." Install it in one CI branch, run a full resolve from scratch with no lockfile, and diff the result against your committed lockfile. Then deliberately break a constraint and read the error — that is the output your team and your scripts will be living with. Grep your pipeline for anything that matches on Bundler's stderr, and grep your config for no_prune.
The broader pattern is worth noticing: package managers across ecosystems are converging on the same feature set within weeks of each other — cooldowns, content addressing, provenance, cross-language resolution. pnpm 12.4 resolving PyPI and crates.io is the same convergence from the JavaScript side. If you want to see who else is building in this space, the developer tools category is the shortest route.
FAQ
Will PubGrub change my Gemfile.lock?
Testers on the migration pull request reported lockfiles remaining unchanged after the switch. That is evidence, not a guarantee — where a dependency graph admits several valid solutions, two resolvers can legitimately choose differently. Resolve from scratch without a lockfile and diff before you trust it.
Is the PubGrub resolver opt-in?
No. The release notes list "Replace Molinillo with PubGrub for dependency resolution" as a default change in 4.1.0.beta1, and the resolver is shared between RubyGems and Bundler.
Do I need to do anything about ML-DSA gem signing?
Only if you already sign gems with RubyGems' certificate workflow. If you do not, the change adds an algorithm option to a path you are not using. Build provenance from your CI is the more widely adopted trust signal.
What does --cooldown actually prevent?
It declines to install versions younger than the age you configure, which buys time for a bad release to be yanked. It does not inspect package contents and it does not stop extension or plugin code from running at install time — --no-build-extension and --no-install-plugin are the flags for that.
Which projects should test the beta now rather than wait?
Gems with native extensions and RubyGems plugins. Four of the five breaking changes touch extension building, plugin loading or removed API, and those are the surfaces a final release will not soften.
When was 4.1.0.beta1 released?
9 September 2026, announced on the RubyGems blog, covering both the rubygems-update and bundler 4.1.0.beta1 releases. No final 4.1.0 has been announced at the time of writing.
A new resolver is a behaviour change dressed as an implementation detail. Read the error messages before you read the changelog.


