<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SHIBATA Hiroshi</title>
    <description>The latest articles on DEV Community by SHIBATA Hiroshi (@hsbt).</description>
    <link>https://dev.to/hsbt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F44575%2F35bf85fc-f4e7-44d1-ad78-eb59e62a2669.jpeg</url>
      <title>DEV Community: SHIBATA Hiroshi</title>
      <link>https://dev.to/hsbt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hsbt"/>
    <language>en</language>
    <item>
      <title>What's New in RubyGems/Bundler 4.1, Part 1: override and prune</title>
      <dc:creator>SHIBATA Hiroshi</dc:creator>
      <pubDate>Fri, 09 Oct 2026 05:22:10 +0000</pubDate>
      <link>https://dev.to/hsbt/whats-new-in-rubygemsbundler-41-part-1-override-and-prune-2ngn</link>
      <guid>https://dev.to/hsbt/whats-new-in-rubygemsbundler-41-part-1-override-and-prune-2ngn</guid>
      <description>&lt;p&gt;I'm Hiroshi Shibata (hsbt), a Ruby committer and the lead maintainer of RubyGems and Bundler.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;RubyGems and Bundler 4.1.0.beta2 shipped on October 8, 2026. This is Part 1 of a three-part series on what's new in 4.1, and it covers two features. &lt;code&gt;override&lt;/code&gt; in the Gemfile rewrites version constraints declared by your dependencies, transitive ones included, and can even drop or replace a dependency. The &lt;code&gt;prune&lt;/code&gt; setting makes Bundler delete download caches and &lt;code&gt;.git&lt;/code&gt; directories after install, replacing the &lt;code&gt;rm -rf&lt;/code&gt; line in your Dockerfile. Both hand back to you a decision RubyGems and Bundler used to make on their own, and both are opt-in. Nothing changes until you ask for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;override&lt;/code&gt; in the Gemfile
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When an upper bound gets in your way
&lt;/h3&gt;

&lt;p&gt;A gem you depend on declares something like &lt;code&gt;&amp;lt; 2.0&lt;/code&gt;. You know the newer version works, but the resolver still refuses. The same thing happens with Ruby itself. If a gemspec's &lt;code&gt;required_ruby_version&lt;/code&gt; has an upper bound like &lt;code&gt;&amp;lt; 4.1&lt;/code&gt;, the gem stops installing the moment you upgrade to Ruby 4.1, even if it actually works.&lt;/p&gt;

&lt;p&gt;Preview releases hide this problem. RubyGems sees a preview or rc Ruby as a prerelease version like &lt;code&gt;4.1.0.preview1&lt;/code&gt;, which satisfies &lt;code&gt;&amp;lt; 4.1&lt;/code&gt;. You can test everything on the preview and still hit the wall on release day.&lt;/p&gt;

&lt;p&gt;Until now, you could fork the gem and maintain it yourself, or wait for upstream to relax the constraint. Both cost far too much for a single line.&lt;/p&gt;

&lt;p&gt;The request is old. &lt;a href="https://github.com/ruby/rubygems/pull/4178" rel="noopener noreferrer"&gt;ruby/rubygems#4178&lt;/a&gt; was opened in December 2020 and stayed open until May 2026, a week after &lt;code&gt;override&lt;/code&gt; was merged. I also tried adding a setting like &lt;code&gt;ignore_ruby_upper_bounds&lt;/code&gt; in &lt;a href="https://github.com/ruby/rubygems/pull/9454" rel="noopener noreferrer"&gt;ruby/rubygems#9454&lt;/a&gt;, but &lt;a href="https://github.com/byroot" rel="noopener noreferrer"&gt;byroot&lt;/a&gt; and &lt;a href="https://github.com/Edouard-chin" rel="noopener noreferrer"&gt;Edouard-chin&lt;/a&gt; pointed out in review that this belonged in the Gemfile DSL. After the discussion in &lt;a href="https://github.com/ruby/rubygems/discussions/9494" rel="noopener noreferrer"&gt;ruby/rubygems#9494&lt;/a&gt;, we settled on the current &lt;code&gt;override&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The target is a gem name string or &lt;code&gt;:all&lt;/code&gt;. The field is &lt;code&gt;version:&lt;/code&gt;, &lt;code&gt;required_ruby_version:&lt;/code&gt;, or &lt;code&gt;required_rubygems_version:&lt;/code&gt;, for the dependency's version requirement, the Ruby version requirement, and the RubyGems version requirement. There are three operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A requirement string&lt;/strong&gt; replaces the existing constraint entirely. The original constraint is discarded, whether it comes from a direct or a transitive dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;:ignore_upper&lt;/code&gt;&lt;/strong&gt; removes the upper-bound operators &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;lt;=&lt;/code&gt;. &lt;code&gt;~&amp;gt;&lt;/code&gt; collapses into a &lt;code&gt;&amp;gt;=&lt;/code&gt; that keeps only the lower bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nil&lt;/code&gt;&lt;/strong&gt; drops every constraint and leaves &lt;code&gt;&amp;gt;= 0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what each operation does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original         Operation        Result
&amp;gt;= 1.0, &amp;lt; 2.0    "&amp;gt;= 8.0"         &amp;gt;= 8.0
~&amp;gt; 1.5           :ignore_upper    &amp;gt;= 1.5
&amp;gt;= 1.0, &amp;lt; 2.0    :ignore_upper    &amp;gt;= 1.0
!= 1.2, &amp;lt; 2.0    :ignore_upper    != 1.2
= 0.9.1          nil              &amp;gt;= 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping &lt;code&gt;!= 1.2&lt;/code&gt; under &lt;code&gt;:ignore_upper&lt;/code&gt; is intentional. The first implementation used an allow-list that kept only lower bounds, but that brought back versions the user had explicitly excluded, such as one with a known vulnerability. Review caught it, and I switched to a reject-list that drops only the upper-bound operators.&lt;/p&gt;

&lt;h3&gt;
  
  
  It reaches transitive dependencies
&lt;/h3&gt;

&lt;p&gt;What makes &lt;code&gt;override&lt;/code&gt; worth having is that it works on dependencies you never wrote yourself. Say &lt;code&gt;qux&lt;/code&gt; depends on &lt;code&gt;bar (&amp;lt; 2.0)&lt;/code&gt;, and &lt;code&gt;bar&lt;/code&gt; 2.0 is out but you can't use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"qux"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolving this Gemfile stops &lt;code&gt;bar&lt;/code&gt; at 1.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    bar (1.0)
    qux (1.0)
      bar (&amp;lt; 2.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add one line of &lt;code&gt;override&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;
&lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;version: :ignore_upper&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"qux"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;bundle update bar&lt;/code&gt;, and &lt;code&gt;bar&lt;/code&gt; moves to 2.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    bar (2.0)
    qux (1.0)
      bar (&amp;lt; 2.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gemspec of &lt;code&gt;qux&lt;/code&gt; hasn't changed, so the lockfile still records &lt;code&gt;bar (&amp;lt; 2.0)&lt;/code&gt; under it. Yet the resolved version is 2.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lockfile doesn't record &lt;code&gt;override&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;That lockfile looks contradictory because &lt;code&gt;override&lt;/code&gt; itself is never written to it. &lt;code&gt;Gemfile.lock&lt;/code&gt; holds only the result of resolution, not which constraints were rewritten or how. This was a design decision from byroot's review. The Gemfile already holds the &lt;code&gt;override&lt;/code&gt;, so recording it again in the lockfile adds no information.&lt;/p&gt;

&lt;p&gt;Since nothing is recorded, adding an &lt;code&gt;override&lt;/code&gt; to an existing lockfile keeps the locked version as long as it satisfies the rewritten constraint. That's why the example above needed &lt;code&gt;bundle update bar&lt;/code&gt;. Bundler can't tell from the lockfile whether the &lt;code&gt;override&lt;/code&gt; was just added, and re-resolving whenever one is present would update gems on every &lt;code&gt;bundle install&lt;/code&gt;. If the locked version no longer satisfies the constraint, as with &lt;code&gt;version: "&amp;gt;= 2.0"&lt;/code&gt;, Bundler re-resolves right away.&lt;/p&gt;

&lt;p&gt;The flip side is that the lockfile alone can't tell you why a particular version was chosen. When you use &lt;code&gt;override&lt;/code&gt;, leave the reason as a comment in the Gemfile. Whoever touches it next will thank you.&lt;/p&gt;

&lt;p&gt;When resolution fails, Bundler lists the active overrides at the end of the error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bundler applied the following overrides while resolving:
  override "bar", version: "= 3.0" (declared at Gemfile:2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ruby and RubyGems version requirements
&lt;/h3&gt;

&lt;p&gt;The other two fields apply to the Ruby and RubyGems requirements a gem declares in its metadata. They're for the moment right after a Ruby upgrade, when a gem that hasn't caught up blocks you with its &lt;code&gt;required_ruby_version&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="ss"&gt;:all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required_ruby_version: :ignore_upper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only these two fields accept &lt;code&gt;:all&lt;/code&gt;, which targets every gem at once. &lt;code&gt;:all&lt;/code&gt; with &lt;code&gt;version:&lt;/code&gt; is rejected, because a dependency's version requirement only makes sense per gem. When a gem has both a per-gem entry and an &lt;code&gt;:all&lt;/code&gt; entry, the per-gem one wins.&lt;/p&gt;

&lt;p&gt;As with &lt;code&gt;version:&lt;/code&gt;, adding this to an existing lockfile keeps the locked versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Before adding                 selectable (1.0)
2. Add :all, run bundle lock     selectable (1.0)
3. Then run bundle update        selectable (2.0)
4. Resolve without a lockfile    selectable (2.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;:all&lt;/code&gt; covers every gem in one line, so without this behavior it would move unrelated gems too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dropping or replacing a dependency with &lt;code&gt;from:&lt;/code&gt; and &lt;code&gt;to:&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;4.1.0.beta2 adds &lt;code&gt;from:&lt;/code&gt; and &lt;code&gt;to:&lt;/code&gt;, which rewrite the dependency itself instead of its version constraint. This started with byroot asking in &lt;a href="https://github.com/ruby/rubygems/pull/9517" rel="noopener noreferrer"&gt;ruby/rubygems#9517&lt;/a&gt; whether there was a way to avoid installing a problematic dependency at all. For example, &lt;code&gt;jekyll&lt;/code&gt; only loads &lt;code&gt;em-websocket&lt;/code&gt; for &lt;code&gt;jekyll serve --livereload&lt;/code&gt;, yet always installs it.&lt;/p&gt;

&lt;p&gt;Let's go back to &lt;code&gt;qux&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt;. To drop the dependency of &lt;code&gt;qux&lt;/code&gt; on &lt;code&gt;bar&lt;/code&gt;, set &lt;code&gt;from:&lt;/code&gt; to the depending gem and &lt;code&gt;to:&lt;/code&gt; to &lt;code&gt;nil&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;
&lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="s2"&gt;"qux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"qux"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After resolving, &lt;code&gt;bar&lt;/code&gt; disappears from the lockfile's gem list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    qux (1.0)
      bar (&amp;lt; 2.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bar (&amp;lt; 2.0)&lt;/code&gt; stays under &lt;code&gt;qux&lt;/code&gt; as published, because the lockfile records a gem's declaration without rewriting it, just as with version overrides. Remove the &lt;code&gt;override&lt;/code&gt; line and resolve again, and &lt;code&gt;bar&lt;/code&gt; comes back at 1.0.&lt;/p&gt;

&lt;p&gt;To replace it with another gem, put that gem's name in &lt;code&gt;to:&lt;/code&gt;. This is for cases where a lighter, API-compatible gem exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;
&lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="s2"&gt;"qux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"bar-lite"&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"qux"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    bar-lite (1.0)
    qux (1.0)
      bar (&amp;lt; 2.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt; 2.0&lt;/code&gt; that &lt;code&gt;qux&lt;/code&gt; put on &lt;code&gt;bar&lt;/code&gt; is dropped. If the replacement needs a version constraint, add &lt;code&gt;version:&lt;/code&gt; alongside, as in &lt;code&gt;to: "bar-lite", version: "&amp;gt;= 2.0"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Bundler doesn't look at what a gem &lt;code&gt;require&lt;/code&gt;s at runtime. If &lt;code&gt;qux&lt;/code&gt; loads &lt;code&gt;bar&lt;/code&gt; unconditionally, dropping it gives you a &lt;code&gt;LoadError&lt;/code&gt;. A replacement only works when it provides the files &lt;code&gt;qux&lt;/code&gt; loads. The replacement also doesn't take the original name, so &lt;code&gt;bar&lt;/code&gt; won't appear in &lt;code&gt;Gem.loaded_specs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An override that would leave the original gem in the bundle anyway is an error. You can't drop a gem listed directly in the Gemfile. If you try to replace &lt;code&gt;bar&lt;/code&gt; while another gem still depends on it, Bundler asks you to write an &lt;code&gt;override&lt;/code&gt; for each dependent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override "bar", from: "qux", to: "bar-lite" (declared at Gemfile:2) would leave
both bar and bar-lite in the bundle because bar is still required by:
  corge
Replace it for each of them, for example:
  override "bar", from: "corge", to: "bar-lite"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;prune&lt;/code&gt; in Bundler
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Turning the Dockerfile &lt;code&gt;rm -rf&lt;/code&gt; into a setting
&lt;/h3&gt;

&lt;p&gt;The Dockerfile that Rails generates contains this line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.bundle/ &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ruby/&lt;span class="k"&gt;*&lt;/span&gt;/cache &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ruby/&lt;span class="k"&gt;*&lt;/span&gt;/bundler/gems/&lt;span class="k"&gt;*&lt;/span&gt;/.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;bundle install&lt;/code&gt;, it deletes the download cache and the &lt;code&gt;.git&lt;/code&gt; directories of git gems. Neither needs to stay in the image, and keeping them only wastes space. &lt;code&gt;prune&lt;/code&gt; moves this hand-written cleanup into Bundler. It was requested in &lt;a href="https://github.com/ruby/rubygems/discussions/7018" rel="noopener noreferrer"&gt;ruby/rubygems#7018&lt;/a&gt; back in 2020 and landed in &lt;a href="https://github.com/ruby/rubygems/pull/9815" rel="noopener noreferrer"&gt;ruby/rubygems#9815&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;prune&lt;/code&gt; only removes artifacts that Bundler creates for its own purposes and can rebuild from the lockfile. It never touches gem contents. There are two categories. &lt;code&gt;cache&lt;/code&gt; is the cache directory under the bundle path, which holds both &lt;code&gt;.gem&lt;/code&gt; files and git mirrors. &lt;code&gt;git&lt;/code&gt; is the &lt;code&gt;.git&lt;/code&gt; directory of checked-out git gems. These match the two parts of the &lt;code&gt;rm -rf&lt;/code&gt; above that Bundler can handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--local&lt;/span&gt; prune cache:git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment variable is &lt;code&gt;BUNDLE_PRUNE&lt;/code&gt;. Categories can be separated by spaces or &lt;code&gt;:&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BUNDLE_PRUNE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache:git bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any value that isn't a category name is read as a boolean, and a true value enables every category. byroot asked for this. A Dockerfile or buildpack can set &lt;code&gt;BUNDLE_PRUNE=1&lt;/code&gt; without knowing the category names and pick up new categories as they're added.&lt;/p&gt;

&lt;p&gt;One thing to watch out for. Only &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;no&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, and the empty string count as false. This vocabulary is shared by every Bundler setting, so &lt;code&gt;BUNDLE_PRUNE=off&lt;/code&gt; and &lt;code&gt;BUNDLE_PRUNE=none&lt;/code&gt; are true, which means prune everything.&lt;/p&gt;

&lt;p&gt;Pruning runs after a successful &lt;code&gt;bundle install&lt;/code&gt;, &lt;code&gt;bundle update&lt;/code&gt;, or &lt;code&gt;bundle cache&lt;/code&gt;, and during &lt;code&gt;bundle clean&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  It needs a bundle path
&lt;/h3&gt;

&lt;p&gt;If you set &lt;code&gt;prune&lt;/code&gt; without configuring a bundle path, nothing is deleted and you get only a warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The `prune` setting was ignored because this bundle installs into the system gem
directory, which Bundler shares with RubyGems. Run `bundle config set --local
path vendor/bundle` to prune.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Bundler 4, &lt;code&gt;path&lt;/code&gt; is unset by default, and gems go into the system gem directory. Its cache is shared with RubyGems and also holds gem files that &lt;code&gt;gem install&lt;/code&gt; put there without Bundler knowing. If &lt;code&gt;prune&lt;/code&gt; deleted those, the damage would reach beyond what Bundler manages, so it only runs when a bundle path is set. Once Bundler 5 makes &lt;code&gt;.bundle&lt;/code&gt; the default &lt;code&gt;path&lt;/code&gt;, this guard will simply stop firing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting things back after pruning
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;prune&lt;/code&gt; only deletes things that can be regenerated, but they don't come back on their own. Pruned artifacts return only when Bundler reinstalls the gem. A plain &lt;code&gt;bundle install&lt;/code&gt; fetches nothing for gems that are already installed, so the cache stays empty. Use &lt;code&gt;bundle install --redownload&lt;/code&gt; to fetch them again. &lt;code&gt;bundle pristine&lt;/code&gt; works once the cache is back. If the &lt;code&gt;prune&lt;/code&gt; setting is still there, though, they get deleted again at the end of the reinstall.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;no_prune&lt;/code&gt; is now &lt;code&gt;keep_outdated_cache&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;There's an existing setting with a similar name, &lt;code&gt;no_prune&lt;/code&gt;. It controls whether &lt;code&gt;bundle cache&lt;/code&gt; removes outdated gems from &lt;code&gt;vendor/cache&lt;/code&gt;, and it has nothing to do with the new &lt;code&gt;prune&lt;/code&gt;. The name reads as if it disabled the new &lt;code&gt;prune&lt;/code&gt;, so &lt;a href="https://github.com/ruby/rubygems/pull/9816" rel="noopener noreferrer"&gt;ruby/rubygems#9816&lt;/a&gt; renamed it to &lt;code&gt;keep_outdated_cache&lt;/code&gt;. The polarity is the same, and true still means keep. The old name is still read, but it prints a deprecation warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[DEPRECATED] The `no_prune` setting has been renamed to `keep_outdated_cache` and will be removed in Bundler 5. Use `keep_outdated_cache` instead.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The old name goes away in Bundler 5, so if your config has &lt;code&gt;BUNDLE_NO_PRUNE&lt;/code&gt;, update it now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Neither &lt;code&gt;override&lt;/code&gt; nor &lt;code&gt;prune&lt;/code&gt; changes anything just because you upgrade to 4.1. Until now, RubyGems and Bundler decided on their own which constraints to resolve against and which install artifacts to keep. With 4.1, people who need to can make those calls themselves.&lt;/p&gt;

&lt;p&gt;4.1.0.beta2 is on rubygems.org, so you can try it today.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem update &lt;span class="nt"&gt;--system&lt;/span&gt; 4.1.0.beta2
gem &lt;span class="nb"&gt;install &lt;/span&gt;bundler &lt;span class="nt"&gt;-v&lt;/span&gt; 4.1.0.beta2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anything behaves oddly during the beta, please report it to &lt;a href="https://github.com/ruby/rubygems" rel="noopener noreferrer"&gt;ruby/rubygems&lt;/a&gt;. Parts 2 and 3 will cover the rest of the 4.1 features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Written in October 2026 against 4.1.0.beta2. Details may change before the final release.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rubygems</category>
      <category>bundler</category>
      <category>packaging</category>
    </item>
    <item>
      <title>What Active Rubyists Are Using in 2026: A Maintainer's Read of the RubyKaigi Survey</title>
      <dc:creator>SHIBATA Hiroshi</dc:creator>
      <pubDate>Fri, 12 Jun 2026 06:15:43 +0000</pubDate>
      <link>https://dev.to/hsbt/what-active-rubyists-are-using-in-2026-a-maintainers-read-of-the-rubykaigi-survey-16p6</link>
      <guid>https://dev.to/hsbt/what-active-rubyists-are-using-in-2026-a-maintainers-read-of-the-rubykaigi-survey-16p6</guid>
      <description>&lt;p&gt;I'm Hiroshi Shibata (hsbt), a Ruby committer and the maintainer of RubyGems and Bundler.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://rubykaigi.org/2026" rel="noopener noreferrer"&gt;RubyKaigi 2026&lt;/a&gt; we ran a 7-question survey on what Ruby developers are actually using: Ruby version, editor, coding agent, version manager, local dev environment. Around 400 responses. The headlines: &lt;strong&gt;Ruby 4.0 adoption is already on par with 3.4 less than six months after release&lt;/strong&gt;, &lt;strong&gt;Claude Code is used by around 80% of respondents&lt;/strong&gt;, &lt;strong&gt;VS Code + Cursor dominate the editor space&lt;/strong&gt;, and &lt;strong&gt;mise / asdf are catching up to rbenv&lt;/strong&gt;. This post walks through the numbers with a maintainer's commentary on each.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Data
&lt;/h2&gt;

&lt;p&gt;The survey was collected at the ANDPAD booth at &lt;a href="https://rubykaigi.org/2026/" rel="noopener noreferrer"&gt;RubyKaigi 2026&lt;/a&gt; (April 2026). Around 400 unique respondents, out of a conference crowd of well over 1,000 attendees. Q1 and Q2 were single-select. Q3 onward allowed multiple selections. Bar heights below represent the absolute number of people choosing each option, not market share. Charts are in Japanese; the bullet list under each chart translates the labels.&lt;/p&gt;

&lt;p&gt;This is not a global Ruby survey. RubyKaigi is held in Japan and the audience is Japan-centric, though it includes a significant number of overseas Rubyists who travel in for the conference. Compared to the &lt;a href="https://survey.stackoverflow.co/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey&lt;/a&gt;, this population skews heavily toward actively-shipping professionals who attend in-person Ruby events. Read it as a snapshot of "Rubyists who still show up in person in 2026," not a representative cross-section of all Ruby users. One more caveat: I maintain several of the tools discussed below, so this is my read of the numbers, not a neutral analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q1. How Many Years Have You Been Using Ruby?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyj7o72i7b6dpr0h36ca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyj7o72i7b6dpr0h36ca.png" alt="Bar chart of years of Ruby experience: 0-2 years about 115, 3-5 years about 95, 6-9 years about 65, 10+ years about 100" width="691" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0–2 years: ~115&lt;/li&gt;
&lt;li&gt;3–5 years: ~95&lt;/li&gt;
&lt;li&gt;6–9 years: ~65&lt;/li&gt;
&lt;li&gt;10+ years: ~100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A U-shaped distribution: newcomers and 10+ year veterans are both large groups, with a thinner middle. Ruby is past 30 years old now, and the fact that both ends of the experience curve are this thick suggests the community is renewing itself while keeping its long-term core intact.&lt;/p&gt;

&lt;p&gt;Globally, AI coding tools have made small teams pick Rails again ("let's build it in Rails one more time"), while large-scale users like Shopify and GitHub keep pushing the upper end forward. The "I just started writing Ruby" voices I heard at the booth match the survey shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q2. What Ruby Version Is Your Main Production Project On?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xe7d2ad3p8reg0msaso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xe7d2ad3p8reg0msaso.png" alt="Bar chart of main production Ruby versions: 3.2 or earlier about 60, 3.3 about 50, 3.4 about 145, 4.0 about 140, 4.1 or later about 15" width="697" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby 3.2 or earlier: ~60&lt;/li&gt;
&lt;li&gt;Ruby 3.3: ~50&lt;/li&gt;
&lt;li&gt;Ruby 3.4: ~145&lt;/li&gt;
&lt;li&gt;Ruby 4.0 (stable): ~140&lt;/li&gt;
&lt;li&gt;Ruby 4.1+ (master / dev): ~15&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the result that surprised me most. Ruby 4.0 was released on December 25, 2025, less than six months before this survey, and it's already on par with 3.4 as the primary production version. As a maintainer team we deliberately kept the 3.x → 4.0 migration cost small even though 4.0 is a major bump, and gem compatibility moved fast. Many projects are already making 4.0 the primary version in CI. That design call appears to be paying off.&lt;/p&gt;

&lt;p&gt;A meaningful number of teams are still on 3.3 or earlier, which is the practical reality of long-running services. For context, Ruby ships a new minor version every Christmas, and each series is supported for roughly three years. Ruby 3.2 is already EOL, and Ruby 3.3 moved to security maintenance in March 2026, meaning it receives security fixes only. If you're on either, plan your upgrade to 3.4 or 4.0 within that window. If something specific is blocking the upgrade, we maintainers would love to hear about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q3. What Editors Do You Use at Work?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexh9glugjpuy5qoxn5y2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexh9glugjpuy5qoxn5y2.png" alt="Bar chart of editors used at work: VS Code about 200, Cursor about 110, Neovim and Vim about 70, RubyMine about 45, Zed about 15, Emacs about 10, others a handful each" width="697" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;: ~200&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cursor.com/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;: ~110&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://neovim.io/" rel="noopener noreferrer"&gt;Neovim&lt;/a&gt; / &lt;a href="https://www.vim.org/" rel="noopener noreferrer"&gt;Vim&lt;/a&gt;: ~70&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.jetbrains.com/ruby/" rel="noopener noreferrer"&gt;RubyMine&lt;/a&gt;: ~45&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zed.dev/" rel="noopener noreferrer"&gt;Zed&lt;/a&gt;: ~15&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.gnu.org/software/emacs/" rel="noopener noreferrer"&gt;Emacs&lt;/a&gt;: ~10&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sublimetext.com/" rel="noopener noreferrer"&gt;Sublime&lt;/a&gt; / Cmux / &lt;a href="https://antigravity.google/" rel="noopener noreferrer"&gt;Antigravity&lt;/a&gt; / &lt;a href="https://helix-editor.com/" rel="noopener noreferrer"&gt;Helix&lt;/a&gt;: a handful each&lt;/li&gt;
&lt;li&gt;Other: ~10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the first multi-select question, so the counts overlap. Some respondents run VS Code alongside Cursor, or pair Neovim with VS Code for completion. Even accounting for that overlap, VS Code is the clear top at ~200, followed by Cursor at ~110.&lt;/p&gt;

&lt;p&gt;VS Code has been the top editor in &lt;a href="https://www.jetbrains.com/lp/devecosystem-2024/" rel="noopener noreferrer"&gt;JetBrains' State of Developer Ecosystem&lt;/a&gt; and the Stack Overflow Developer Survey for years. What's new in 2026 is the rise of Cursor and Zed. Editor choice is increasingly driven by LSP support (notably &lt;a href="https://github.com/Shopify/ruby-lsp" rel="noopener noreferrer"&gt;ruby-lsp&lt;/a&gt;) and the quality of built-in AI assist, rather than by editor traditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q4. What Coding Agent Do You Use at Work?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4bnw52dsixftigtpba9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4bnw52dsixftigtpba9.png" alt="Bar chart of coding agents used at work: Claude Code about 325, GitHub Copilot about 85, Copilot Workspace about 60, Codex about 25, Devin about 10, not allowed or not using about 10, others a few each" width="685" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was the most skewed result of the entire survey.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;: ~325&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; (Chat / Autocomplete): ~85&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://githubnext.com/projects/copilot-workspace" rel="noopener noreferrer"&gt;GitHub Copilot Workspace&lt;/a&gt;: ~60&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openai.com/codex/" rel="noopener noreferrer"&gt;Codex&lt;/a&gt;: ~25&lt;/li&gt;
&lt;li&gt;Not allowed by company policy / not using: ~10&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devin.ai/" rel="noopener noreferrer"&gt;Devin&lt;/a&gt;: ~10&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/q/developer/" rel="noopener noreferrer"&gt;Amazon Q Developer&lt;/a&gt; / &lt;a href="https://cline.bot/" rel="noopener noreferrer"&gt;Cline&lt;/a&gt; / &lt;a href="https://github.com/RooCodeInc/Roo-Code" rel="noopener noreferrer"&gt;Roo Code&lt;/a&gt; / &lt;a href="https://gemini.google.com/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; / other: a few each&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude Code at ~325 is roughly 80% of respondents. The takeaway is blunt. If you're using a coding agent in 2026, you're almost certainly using Claude Code. Among Ruby developers attending RubyKaigi, it's the effective default.&lt;/p&gt;

&lt;p&gt;The "not allowed by company policy" cluster is small but worth naming. The practical questions are still not fully solved at every org: what code or customer data leaves the company, vendor lock-in, and contract language for LLM-bound data flows. The unglamorous work of "making coding agents usable internally" is going to be a real competitive factor for engineering organizations over the next year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q5. What Are You Building with Coding Agents?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrxone0dncr3lc5a9ri3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrxone0dncr3lc5a9ri3.png" alt="Bar chart of what respondents build with coding agents: production code at work about 320, hobby projects about 165, internal tooling about 160, OSS used by your product about 50, other OSS about 35" width="694" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production code at work: ~320&lt;/li&gt;
&lt;li&gt;Hobby projects: ~165&lt;/li&gt;
&lt;li&gt;Internal tooling at work: ~160&lt;/li&gt;
&lt;li&gt;OSS used by your product: ~50&lt;/li&gt;
&lt;li&gt;Other OSS: ~35&lt;/li&gt;
&lt;li&gt;Other: ~5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total votes are around 735, which means each respondent uses coding agents for about two categories on average. Production code at ~320 is unsurprising. Essentially everyone using a coding agent is using it at work. What's more interesting is that "hobby projects" (~165) and "internal tooling" (~160) sit at roughly half the production-code volume. For myself, since I started handing maintenance of Ruby-adjacent tools (like &lt;a href="https://github.com/ruby/all-ruby" rel="noopener noreferrer"&gt;all-ruby&lt;/a&gt;) and release/packaging scripts to Claude Code, I write less code by hand but ship more.&lt;/p&gt;

&lt;p&gt;Combining "OSS used by your product" (~50) and "other OSS" (~35), roughly 85 respondents are bringing coding agents into OSS work. Even after deduplicating, that's around 20% of respondents using coding agents to maintain open source. That would have been hard to imagine a year ago. For the Ruby ecosystem this directly lowers maintenance burden, and I see the effect firsthand on &lt;a href="https://rubygems.org/" rel="noopener noreferrer"&gt;RubyGems&lt;/a&gt; and &lt;a href="https://bundler.io/" rel="noopener noreferrer"&gt;Bundler&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q6. What Do You Use for Package and Version Management?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxbip6j0n2m76vv4zg8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxbip6j0n2m76vv4zg8l.png" alt="Bar chart of version management tools: rbenv and other Ruby-specific tools about 200, mise and asdf about 145, Homebrew about 140, Dev Containers about 60, Nix family about 10" width="698" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby-specific tools like &lt;a href="https://github.com/rbenv/rbenv" rel="noopener noreferrer"&gt;rbenv&lt;/a&gt;: ~200&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mise.jdx.dev/" rel="noopener noreferrer"&gt;mise&lt;/a&gt; / &lt;a href="https://asdf-vm.com/" rel="noopener noreferrer"&gt;asdf&lt;/a&gt;: ~145&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; (project-local): ~140&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;Dev Containers&lt;/a&gt;: ~60&lt;/li&gt;
&lt;li&gt;Nix-family (&lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;nix&lt;/a&gt; / &lt;a href="https://www.jetify.com/devbox" rel="noopener noreferrer"&gt;devbox&lt;/a&gt;): ~10&lt;/li&gt;
&lt;li&gt;Other: ~10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm one of the rbenv maintainers, so factor in my bias. Still, the rbenv family is the largest group at ~200, with mise/asdf catching up at ~145. As more developers move between Node.js, Python, Go, and Ruby in the same week, the appeal of "one tool for all languages" instead of "one version manager per language" is real. Some portion of the rbenv user base is clearly in the middle of switching to or co-running mise.&lt;/p&gt;

&lt;p&gt;Homebrew (project-local) sits at ~140, the same range as mise/asdf. I suspect this is a signal that more people are running their project Ruby inside Docker or in a remote dev environment and simply don't need to switch versions on the host anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q7. How Do You Run Your Project Locally?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9ntjwhht41864nidori.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9ntjwhht41864nidori.png" alt="Bar chart of local dev environments: Docker Compose about 295, native macOS or Linux about 65, Lima and Colima about 35, OrbStack about 30, Kamal, Codespaces and Podman Desktop about 5 each" width="695" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;: ~295&lt;/li&gt;
&lt;li&gt;Native (macOS / Linux): ~65&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lima-vm.io/" rel="noopener noreferrer"&gt;Lima&lt;/a&gt; / &lt;a href="https://github.com/abiosoft/colima" rel="noopener noreferrer"&gt;Colima&lt;/a&gt;: ~35&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://orbstack.dev/" rel="noopener noreferrer"&gt;OrbStack&lt;/a&gt;: ~30&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kamal-deploy.org/" rel="noopener noreferrer"&gt;Kamal&lt;/a&gt; (local runner): ~5&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/features/codespaces" rel="noopener noreferrer"&gt;Codespaces&lt;/a&gt; / cloud IDE: ~5&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://podman-desktop.io/" rel="noopener noreferrer"&gt;Podman Desktop&lt;/a&gt;: ~5&lt;/li&gt;
&lt;li&gt;Other: ~5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker Compose at ~295 is roughly three-quarters of respondents. For a Rails app, spinning up web / DB / Redis / job queue together with Docker Compose is effectively the standard local stack.&lt;/p&gt;

&lt;p&gt;Lima / Colima at ~35 and OrbStack at ~30 are neck-and-neck for the "Linux containers on Apple Silicon" slot. I'm personally curious how Apple's official &lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;&lt;code&gt;container&lt;/code&gt; command&lt;/a&gt;, introduced in macOS 26, will land here over the next year. Native (macOS / Linux) at ~65 is the segment that runs Ruby / PostgreSQL / Redis directly on the host. It's simpler and noticeably faster than going through a container layer, and I work this way most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Says About Ruby in 2026
&lt;/h2&gt;

&lt;p&gt;Seven questions, and the picture that comes out is a community that's mature but tooling-wise in a very dynamic phase.&lt;/p&gt;

&lt;p&gt;Ruby 4.0 adoption is moving faster than expected. Editors are converging on VS Code and Cursor, where AI assist is built in. Coding agents are heavily concentrated on Claude Code. Version managers are diversifying away from rbenv-only toward mise / asdf. Every one of these has shifted noticeably in the last 12–24 months. The actual experience of writing Ruby hasn't really changed in 20 years, but the tooling around it is in the middle of a generational turnover right now.&lt;/p&gt;

&lt;p&gt;From the maintainer side, my job is to watch these shifts and keep Ruby and RubyGems / Bundler as the foundation that all this tooling can build on. Supply chain security work, like the &lt;a href="https://github.com/ruby/rubygems/discussions/9113" rel="noopener noreferrer"&gt;cooldown discussion on RubyGems&lt;/a&gt; I wrote about &lt;a href="https://dev.to/hsbt/should-rubygemsbundler-have-a-cooldown-feature-40cp"&gt;earlier this year&lt;/a&gt;, is increasingly cross-ecosystem rather than Ruby-only. The maintenance work going forward is to pick Ruby-shaped solutions that fit into the broader picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Want Global Numbers
&lt;/h2&gt;

&lt;p&gt;This is a Japan-centric snapshot. For a global view, the &lt;a href="https://railsdeveloper.com/survey/" rel="noopener noreferrer"&gt;Rails Developer Survey&lt;/a&gt; run by Rails Foundation is the better reference, and Rails Developer Survey 2026 is open for responses right now. The more practitioners like the ones in this survey respond, the more its results reflect what experienced teams are actually doing in production. It takes 10–15 minutes. Please consider filling it out.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>programming</category>
      <category>survey</category>
      <category>community</category>
    </item>
    <item>
      <title>Is Your Ruby Version Still Supported? A Maintainer's Guide to Ruby's Release Cycle</title>
      <dc:creator>SHIBATA Hiroshi</dc:creator>
      <pubDate>Mon, 06 Apr 2026 07:25:38 +0000</pubDate>
      <link>https://dev.to/hsbt/is-your-ruby-version-still-supported-a-maintainers-guide-to-rubys-release-cycle-799</link>
      <guid>https://dev.to/hsbt/is-your-ruby-version-still-supported-a-maintainers-guide-to-rubys-release-cycle-799</guid>
      <description>&lt;p&gt;I'm Hiroshi Shibata (hsbt), a Ruby committer and one of the branch maintainers responsible for Ruby's stable releases. I also maintain RubyGems and Bundler.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Since the March 2026 security releases of Ruby 3.3.11 and 3.2.11, no critical build issues or other problems have been reported. As announced in each release note, I'm now confirming: &lt;strong&gt;Ruby 3.2 has reached end-of-life&lt;/strong&gt;, and &lt;strong&gt;Ruby 3.3 has moved to security-only maintenance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you're on either version, now is the time to plan your upgrade to Ruby 3.4 or 4.0. This post explains how Ruby's release cycle works, who maintains each branch, and what "security maintenance" actually means in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened in March
&lt;/h2&gt;

&lt;p&gt;On March 26-27, 2026, we released Ruby 3.3.11 and Ruby 3.2.11 — both addressing CVE-2026-27820, a buffer overflow in &lt;code&gt;Zlib::GzipReader&lt;/code&gt;. These weren't ordinary patch releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.ruby-lang.org/en/news/2026/03/27/ruby-3-2-11-released/" rel="noopener noreferrer"&gt;&lt;strong&gt;Ruby 3.2.11&lt;/strong&gt;&lt;/a&gt; is the final release of the 3.2 series. No further updates of any kind will be provided. Ruby 3.2 had been supported for over three years since its initial release on December 25, 2022.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.ruby-lang.org/en/news/2026/03/26/ruby-3-3-11-released/" rel="noopener noreferrer"&gt;&lt;strong&gt;Ruby 3.3.11&lt;/strong&gt;&lt;/a&gt; is the last normal maintenance release of the 3.3 series. From now on, Ruby 3.3 enters security-only maintenance for one year, until March 2027.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of status transition happens every spring, roughly three to four months after the Christmas release of a new Ruby version. If you've been using Ruby for a while, the pattern might feel familiar. But the details of how it works aren't widely documented in English, so here's the full picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby's Versioning: Not Quite Semver
&lt;/h2&gt;

&lt;p&gt;Ruby follows a &lt;a href="https://docs.ruby-lang.org/en/master/maintaining_ruby.html" rel="noopener noreferrer"&gt;&lt;code&gt;MAJOR.MINOR.TEENY&lt;/code&gt;&lt;/a&gt; scheme that looks like semantic versioning but isn't exactly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MAJOR&lt;/strong&gt; increases when Matz decides the time is right. There's no predetermined criteria or schedule — it's entirely his call. The jump from 2.x to 3.0 happened when Ruby 3x3 (3x performance), Ractor (parallelism), and RBS (type signatures) came together. The &lt;a href="https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/" rel="noopener noreferrer"&gt;jump to 4.0&lt;/a&gt; brought ZJIT (a next-generation JIT compiler), Ruby Box (definition isolation), and Ractor improvements. Both bumps reflected Matz's judgment that the accumulated changes warranted a new major version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MINOR&lt;/strong&gt; increases every Christmas. Each December 25, a new stable version ships. API-incompatible changes can and do appear in MINOR releases — for example, libraries moving from default gems to bundled gems, meaning you need to add them to your Gemfile explicitly. This surprises people coming from strict semver ecosystems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TEENY&lt;/strong&gt; increases for bug fixes and security patches, released every two to three months. TEENY releases maintain API compatibility. The number can go above 9 — Ruby 3.2 reached 3.2.11.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Christmas release cadence is one of Ruby's distinctive traits. It means every year, there's a predictable moment when a new version appears and the maintenance window shifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Branch Lifecycle
&lt;/h2&gt;

&lt;p&gt;Ruby doesn't have LTS (Long-Term Support) versions. Instead, every stable branch goes through the same lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normal maintenance&lt;/strong&gt; — receives bug fixes and security fixes. Lasts about two years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security maintenance&lt;/strong&gt; — receives only security fixes and critical build fixes. Lasts about one year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End of life&lt;/strong&gt; — no further releases.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A stable branch typically lives for about three years total. At any given time, we maintain three or four branches simultaneously.&lt;/p&gt;

&lt;p&gt;Here's the current state as of April 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Branch Maintainer&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 4.0&lt;/td&gt;
&lt;td&gt;Normal maintenance&lt;/td&gt;
&lt;td&gt;k0kubun&lt;/td&gt;
&lt;td&gt;Released 2025-12-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4&lt;/td&gt;
&lt;td&gt;Normal maintenance&lt;/td&gt;
&lt;td&gt;nagachika&lt;/td&gt;
&lt;td&gt;Released 2024-12-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.3&lt;/td&gt;
&lt;td&gt;Security maintenance&lt;/td&gt;
&lt;td&gt;hsbt&lt;/td&gt;
&lt;td&gt;Released 2023-12-25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.2&lt;/td&gt;
&lt;td&gt;End of life&lt;/td&gt;
&lt;td&gt;hsbt&lt;/td&gt;
&lt;td&gt;Released 2022-12-25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And here's how the transition works each year:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;December 25: New stable version released (e.g., 3.4.0)
  → 4 branches under maintenance for ~3 months

March-April: Oldest branch reaches EOL
  → Back to 3 branches
  → Second-oldest branch moves to security maintenance

December 25: Next stable version released
  → Cycle repeats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see how this plays out concretely: in March 2025, Ruby 3.1 reached end-of-life with its final release 3.1.7, and Ruby 3.2 moved from normal maintenance to security-only maintenance. One year later, in March 2026, the same thing happened one version up — Ruby 3.2 reached end-of-life and Ruby 3.3 entered security maintenance. If the pattern holds, Ruby 3.3 will reach end-of-life in March 2027 when Ruby 3.4 moves to security maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Maintains What
&lt;/h2&gt;

&lt;p&gt;Ruby's branch maintenance isn't a solo effort, but it's not a large team either. Currently, four people handle all stable releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/nurse" rel="noopener noreferrer"&gt;nurse&lt;/a&gt;&lt;/strong&gt; (naruse) — maintains the development branch (master) and handles the Christmas release of each new stable version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/k0kubun" rel="noopener noreferrer"&gt;k0kubun&lt;/a&gt;&lt;/strong&gt; — maintains the latest stable version (currently Ruby 4.0). Releases every two to three months, sometimes faster when warranted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/nagachika" rel="noopener noreferrer"&gt;nagachika&lt;/a&gt;&lt;/strong&gt; — maintains Ruby 3.4 (normal maintenance).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/hsbt" rel="noopener noreferrer"&gt;hsbt&lt;/a&gt;&lt;/strong&gt; (me) — maintains the security maintenance versions (currently Ruby 3.3) and handles branches until EOL (Ruby 3.2, now concluded). Also handles release infrastructure and automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure was formalized through a &lt;a href="https://bugs.ruby-lang.org/issues/20432" rel="noopener noreferrer"&gt;proposal in 2024&lt;/a&gt; to streamline the teeny release workflow. Before that, branch maintainer assignments were less systematic.&lt;/p&gt;

&lt;p&gt;I sometimes describe my coordination role as "release manager manager" — I don't just release my own branches, but also prepare templates for release announcements, ensure CDN publication works, purge negative caches, and automate as much of the process as possible so that other branch maintainers can ship releases with minimal friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Security Maintenance" Actually Means
&lt;/h2&gt;

&lt;p&gt;The term "security maintenance" suggests we only fix CVEs during this phase. In practice, the scope is broader than that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security fixes&lt;/strong&gt; — any vulnerability with a CVE assignment gets patched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critical build fixes&lt;/strong&gt; — if a supported OS releases a new version and Ruby can't build on it, that gets fixed. Software breaks if you leave it alone; keeping Ruby buildable on current platforms is part of the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test fixes&lt;/strong&gt; — if CI platforms we test against cause test failures, we fix the test code to keep the suite green. This lowers the barrier for future emergency releases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple: if you're running Ruby 3.3 in production for the next year, it should keep building and running on the platforms you actually use. We won't add features or fix minor bugs, but we won't let it rot either.&lt;/p&gt;

&lt;p&gt;If you discover a security vulnerability in Ruby, please report it through &lt;a href="https://hackerone.com/ruby" rel="noopener noreferrer"&gt;HackerOne&lt;/a&gt;. For bundled gems, you can also use &lt;a href="https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability" rel="noopener noreferrer"&gt;GitHub's private vulnerability reporting&lt;/a&gt; on the gem's repository. We've been gradually migrating to GitHub for bundled gems since it allows the maintainers of each gem to handle the process directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Release Gets Made
&lt;/h2&gt;

&lt;p&gt;For a normal teeny release, the process looks roughly like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bug fixes land on master first, then get backported to stable branches. For the latest stable version, contributors actively triage and create patches, which the branch maintainer backports. For older branches, I cherry-pick fixes manually.&lt;/li&gt;
&lt;li&gt;The branch maintainer runs CI across all supported platforms and fixes anything that breaks.&lt;/li&gt;
&lt;li&gt;When ready, the maintainer tags the release. From that point, automation takes over — packages are built, tested, and published.&lt;/li&gt;
&lt;li&gt;The release announcement is posted on ruby-lang.org.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you've found a bug that's been fixed on master but hasn't made it into a stable release yet, you can request a backport. The process is documented in the &lt;a href="https://github.com/ruby/ruby/wiki/How-To-Request-Backport" rel="noopener noreferrer"&gt;How To Request Backport&lt;/a&gt; wiki page. In short: file a ticket on &lt;a href="https://bugs.ruby-lang.org/" rel="noopener noreferrer"&gt;bugs.ruby-lang.org&lt;/a&gt; with the commit hash in the subject (e.g., "Backport abcde123"), and the branch maintainer will pick it up. Generally, only bug fixes are eligible for backport — feature additions stay on master. Even better, you can open a pull request directly against the target branch (e.g., &lt;code&gt;ruby_3_4&lt;/code&gt;) on GitHub. This saves the branch maintainer time and gets your fix into the next release faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;p&gt;If you're on &lt;strong&gt;Ruby 3.2&lt;/strong&gt;: upgrade now. There will be no more releases, not even for security issues.&lt;/p&gt;

&lt;p&gt;If you're on &lt;strong&gt;Ruby 3.3&lt;/strong&gt;: you have until March 2027, but only security fixes will be backported. Start planning your upgrade.&lt;/p&gt;

&lt;p&gt;If you're on &lt;strong&gt;Ruby 3.4 or 4.0&lt;/strong&gt;: you're in good shape. Normal maintenance continues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One exception&lt;/strong&gt;: if you're using Ruby packages provided by a Linux distribution such as RHEL, Ubuntu, or Debian, the upstream EOL dates above don't directly apply to you. Those distributions maintain their own packages and backport security fixes according to their own support lifecycle. Check your distribution's documentation for the actual support timeline of the Ruby version you're running.&lt;/p&gt;

&lt;p&gt;Check the official branch status page for the latest maintenance status: &lt;a href="https://www.ruby-lang.org/en/downloads/branches/" rel="noopener noreferrer"&gt;ruby-lang.org/en/downloads/branches/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For details on what changed in each version, the &lt;a href="https://github.com/ruby/ruby/blob/master/NEWS.md" rel="noopener noreferrer"&gt;NEWS file&lt;/a&gt; in Ruby's source tree is the authoritative reference. If you want to catch deprecation warnings in your application, try running with &lt;code&gt;ruby -w&lt;/code&gt; to enable all warnings including deprecations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Written in April 2026. Branch status and maintainer assignments may change over time.&lt;/li&gt;
&lt;li&gt;I'm a Ruby committer and branch maintainer. This reflects my perspective on how the process works in practice.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>programming</category>
      <category>opensource</category>
      <category>releases</category>
    </item>
    <item>
      <title>Should RubyGems/Bundler Have a Cooldown Feature?</title>
      <dc:creator>SHIBATA Hiroshi</dc:creator>
      <pubDate>Thu, 19 Mar 2026 01:45:21 +0000</pubDate>
      <link>https://dev.to/hsbt/should-rubygemsbundler-have-a-cooldown-feature-40cp</link>
      <guid>https://dev.to/hsbt/should-rubygemsbundler-have-a-cooldown-feature-40cp</guid>
      <description>&lt;p&gt;I'm Hiroshi Shibata (hsbt), a Ruby committer and the maintainer of RubyGems and Bundler.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Every major package manager is adding "cooldown" — a waiting period before you can install newly released packages. RubyGems/Bundler doesn't have one yet. I've been discussing whether we should add it. Short answer: yes, as an opt-in option, but cooldown alone isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Cooldown?
&lt;/h2&gt;

&lt;p&gt;A cooldown prevents upgrading to a new package version until a certain time has passed since its release. The idea is simple: if a malicious package is published, the waiting period gives security researchers time to catch it before it reaches your &lt;code&gt;Gemfile.lock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns" rel="noopener noreferrer"&gt;William Woodruff's analysis&lt;/a&gt; found that 8 out of 10 supply chain attacks he examined had an exploitation window of less than one week. A 7-day cooldown could have prevented most of them.&lt;/p&gt;

&lt;p&gt;As Andrew Nesbitt summarizes in "&lt;a href="https://nesbitt.io/2026/03/04/package-managers-need-to-cool-down.html" rel="noopener noreferrer"&gt;Package Managers Need to Cool Down&lt;/a&gt;", from late 2025 into 2026, cooldown adoption has accelerated rapidly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Landscape
&lt;/h2&gt;

&lt;p&gt;Cooldown started in dependency update tools, then spread to package managers themselves. One complication: every tool picked a different config name — &lt;code&gt;minimumReleaseAge&lt;/code&gt;, &lt;code&gt;min-release-age&lt;/code&gt;, &lt;code&gt;npmMinimalAgeGate&lt;/code&gt;, &lt;code&gt;exclude-newer&lt;/code&gt;, &lt;code&gt;uploaded-prior-to&lt;/code&gt;, and so on. At least 10 different names exist. Polyglot developers, beware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Update Tools
&lt;/h3&gt;

&lt;p&gt;Dependabot added a &lt;code&gt;cooldown&lt;/code&gt; block in July 2025. You can set different waiting periods per semver level, and security updates bypass the cooldown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/dependabot.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bundler"&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly"&lt;/span&gt;
    &lt;span class="na"&gt;cooldown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;semver-major-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Renovate has &lt;code&gt;minimumReleaseAge&lt;/code&gt; (formerly &lt;code&gt;stabilityDays&lt;/code&gt;), with per-package and per-update-type granularity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minimumReleaseAge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3 days"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packageRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchUpdateTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"major"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimumReleaseAge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7 days"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Package Managers
&lt;/h3&gt;

&lt;p&gt;pnpm was first, shipping &lt;code&gt;minimumReleaseAge&lt;/code&gt; in v10.16 (September 2025):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pnpm-workspace.yaml&lt;/span&gt;
&lt;span class="na"&gt;minimumReleaseAge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1440&lt;/span&gt;  &lt;span class="c1"&gt;# 24 hours&lt;/span&gt;
&lt;span class="na"&gt;minimumReleaseAgeExclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@myorg/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;npm followed with &lt;code&gt;min-release-age&lt;/code&gt; in CLI 11.x (February 2026). Bun added &lt;code&gt;minimumReleaseAge&lt;/code&gt; in October 2025, Deno added &lt;code&gt;--minimum-dependency-age&lt;/code&gt;. All major JavaScript runtimes now support cooldowns.&lt;/p&gt;

&lt;p&gt;On the Python side, uv extended its &lt;code&gt;--exclude-newer&lt;/code&gt; flag (originally for reproducible builds) to accept relative durations like &lt;code&gt;"7 days"&lt;/code&gt;. pip introduced &lt;code&gt;--uploaded-prior-to&lt;/code&gt; in 26.0 (January 2026), though pip only takes absolute timestamps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does Ruby Stand?
&lt;/h2&gt;

&lt;p&gt;RubyGems and Bundler have no cooldown feature. A community member opened a &lt;a href="https://github.com/ruby/rubygems/discussions/9113" rel="noopener noreferrer"&gt;Discussion on ruby/rubygems&lt;/a&gt; requesting one, and I discussed it with Ruby community members and the RubyGems team.&lt;/p&gt;

&lt;p&gt;What follows is my take on the discussion. I'm the RubyGems/Bundler maintainer, so keep that bias in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "guinea pig" problem
&lt;/h3&gt;

&lt;p&gt;My first concern. Cooldowns implicitly assume that &lt;em&gt;someone else&lt;/em&gt; will install the new version first and find problems. But if everyone enables cooldowns, nobody tries new releases during the waiting period — and the mechanism becomes useless. Better than nothing, probably. But it's a structural limitation of how OSS works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delayed security fixes
&lt;/h3&gt;

&lt;p&gt;A strict cooldown also delays urgent security patches. Distinguishing a malicious release from a legitimate security fix automatically is extremely difficult. Cooldowns could actually &lt;em&gt;increase&lt;/em&gt; risk in some cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  The illusion of safety
&lt;/h3&gt;

&lt;p&gt;Software that hasn't been updated in 10 years may contain undiscovered vulnerabilities. Consider a gem that's been on RubyGems for years without updates — age tells you nothing about whether it's been audited. Time passing doesn't guarantee security. Cooldowns provide a sense of security, but a sense of security isn't the same as actual security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buying time for security researchers
&lt;/h3&gt;

&lt;p&gt;On the other hand — and this is the strongest argument for cooldowns — they buy time for people who are actually scanning packages.&lt;/p&gt;

&lt;p&gt;A concrete example: &lt;a href="https://mensfeld.pl/" rel="noopener noreferrer"&gt;Maciej Mensfeld&lt;/a&gt;, a member of the RubyGems security team and developer of Diffend (a supply chain security platform for Ruby). He has detected and reported over 20,000 malicious packages across ecosystems.&lt;/p&gt;

&lt;p&gt;In the RubyGems ecosystem specifically, he found over 700 typosquatting gems in 2020 (e.g., &lt;code&gt;rail&lt;/code&gt; targeting &lt;code&gt;rails&lt;/code&gt;), and in 2022, more than 400 malicious packages were removed. His RubyKaigi 2023 talk "&lt;a href="https://rubykaigi.org/2023/presentations/maciejmensfeld.html" rel="noopener noreferrer"&gt;RubyGems on the watch&lt;/a&gt;" covers these efforts.&lt;/p&gt;

&lt;p&gt;Today, the security team including Maciej reviews gems after release on rubygems.org. Gems found to contain malicious code are removed. A cooldown isn't just about waiting — it becomes effective when combined with this kind of scanning infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Considering
&lt;/h2&gt;

&lt;p&gt;My current thinking: offer cooldown as an opt-in option in the short term, pursue more technically effective measures longer term.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundler Cooldown Option
&lt;/h3&gt;

&lt;p&gt;Enterprise environments want this. Dependabot and Renovate already have equivalent features, so it makes sense to support it at the Bundler level too.&lt;/p&gt;

&lt;p&gt;What the spec might look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bundle update --cooldown 3&lt;/code&gt; (CLI option)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bundle config set cooldown 3&lt;/code&gt; (global config)&lt;/li&gt;
&lt;li&gt;Block syntax in the Gemfile for per-gem cooldowns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gem install&lt;/code&gt; support for CI/CD environments like GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opt-in, disabled by default. Users choose based on their own needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scanning and Metadata on RubyGems.org
&lt;/h3&gt;

&lt;p&gt;To make cooldowns actually useful, "has this gem been scanned?" matters as much as "has enough time passed?" On the rubygems.org side, we're considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated scanning of new releases, with scan status published as index metadata&lt;/li&gt;
&lt;li&gt;Delays for high-risk releases (recent ownership changes, Trusted Publishing disabled)&lt;/li&gt;
&lt;li&gt;Letting RubyGems/Bundler reference this metadata to adjust behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also been a suggestion for a "scanned" badge on rubygems.org — worth exploring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond Cooldown
&lt;/h3&gt;

&lt;p&gt;Separately, there's discussion about sandboxing code execution during &lt;code&gt;gem install&lt;/code&gt;: &lt;a href="https://github.com/ruby/rubygems/issues/9138" rel="noopener noreferrer"&gt;ruby/rubygems#9138&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;RubyGems and Bundler recently landed a change that separates the download and install phases: &lt;a href="https://github.com/ruby/rubygems/pull/9381" rel="noopener noreferrer"&gt;ruby/rubygems#9381&lt;/a&gt;. This was for performance, but the separation opens the door to running SAST or other verification after download and before install — potentially more effective than cooldown alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Cooldown is not a silver bullet. But combined with server-side scanning and metadata on rubygems.org, it can be a meaningful layer of defense. We plan to offer it as an opt-in option in RubyGems/Bundler.&lt;/p&gt;

&lt;p&gt;If you have thoughts, join the &lt;a href="https://github.com/ruby/rubygems/discussions/9113" rel="noopener noreferrer"&gt;GitHub Discussion&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Written in March 2026. The ecosystem is evolving quickly — specifics may change.&lt;/li&gt;
&lt;li&gt;I'm a Ruby committer and the maintainer of RubyGems/Bundler. My perspective is shaped by that role.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>security</category>
      <category>supplychainsecurity</category>
      <category>packaging</category>
    </item>
  </channel>
</rss>
