Upgrade Ruby 3.3 to 3.4 on your existing Rails version when that combination is tested. Keeping Rails, its defaults and the application’s dependencies fixed makes new failures easier to locate.
If your current framework or a required gem cannot run there, establish a working intermediate combination first. A Ruby requirement such as >= 2.7 cannot answer that question by itself. Nor does installing the Rails gem prove that its database adapter, debugger or background workers work.
Choose the order from evidence
Checked 7 September 2026, Ruby 3.3 is receiving security fixes, Ruby 3.4 is in normal maintenance, and Ruby 3.2 reached end of life on 1 April 2026. The current patch versions used here are 3.3.12 and 3.4.10. Recheck the Ruby branches and release list when preparing your deployment.
The table combines exact released gemspecs with separately pinned, current non-nightly framework CI selection rules. S means selected; A means selected with failures allowed. Neither means a passing build.
| Rails tag | Ruby floor | 3.1 | 3.2 | 3.3 | 3.4 | 4.0 |
|---|---|---|---|---|---|---|
| 6.1.7.10 | 2.5 | S | S | S | A | A |
| 7.0.10 | 2.7 | S | S | S | A | A |
| 7.1.6 | 2.7 | S | S | S | A | A |
| 7.2.3.2 | 3.1 | S | S | S | A | A |
| 8.0.5.1 | 3.2 | Below floor | S | S | S | S |
| 8.1.3.1 | 3.2 | Below floor | S | S | S | S |
Framework CI lives in another official repository. These pipeline and selection rules were checked on 7 September; they are not historical CI runs preserved in those release tags.
An absent entry means this evidence does not answer the question. Older Rails branches also retain their own support deadlines, regardless of whether a newer Ruby boots them. Check the Rails and Ruby support calendar separately.
The practical order is conditional. Test the current Rails patch and locked gems on both Rubies. If they pass, deploy the Ruby change independently. If they cannot, identify the specific blocker and move Rails or that dependency to a verified intermediate combination. Do not choose “the highest supported Ruby” by interpreting an unbounded gemspec as a compatibility promise.
A library can ship with Ruby and still need a Gemfile entry
Ruby’s standard-library gemification lets libraries receive releases independently of the interpreter. Ruby 2.5’s 2017 release already documented libraries becoming default gems. The operational distinction is between a default gem, available without a Gemfile declaration, and a bundled gem that Bundler must include in the resolved dependency graph. A transitive dependency can supply it, but an application that uses it directly should declare it.
Ruby 3.4’s December 2024 tagged NEWS lists twelve default-to-bundled promotions, including mutex_m, getoptlong, base64, bigdecimal, observer, abbrev, resolv-replace, rinda, drb, nkf, syslog and csv. It separately adds repl_type_completor as a bundled gem. Ruby 3.4 library changes.
A small Gemfile without these dependencies loads bigdecimal, drb and mutex_m under Ruby 3.3.12, with migration warnings. Ruby 3.4.10 raises LoadError for each. One recorded warning is:
warning: bigdecimal was loaded from the standard library, but will no longer be part of the default gems starting from Ruby 3.4.0.
For that reproduction, the repair is explicit:
+gem "bigdecimal", "4.1.2"
+gem "drb", "2.2.3"
+gem "mutex_m", "0.3.0"
Those are the reproduction’s versions, not a prescription to pin every application to them. Add only dependencies your application needs, resolve them against its lockfile, and rerun boot and parallel tests. Rails already repaired several framework dependencies. Active Support 7.1.0’s 2023 release declared bigdecimal, drb, mutex_m and base64; Rails 7.0.9 received backports in 2025. Check the actual patch before adding workarounds for an older report. 7.1 changelog, 7.0.9 changelog.
The logger incident shows why patch precision matters. On 15 January 2025, concurrent-ruby 1.3.5 removed its logger dependency. Rails 7.0.8.7 relied on that load order; an explicit require "logger" before Active Support addressed that historical failure. Rails 7.0.9 included the explicit require, and Rails 7.1.0 already had it. A blanket instruction to pin concurrent-ruby for every Rails 7.0 application is stale. Dependency change, 7.0.9 source, 7.1.0 source.
Check string mutation warnings
Ruby 2.3 introduced the frozen_string_literal comment in 2015 to allow immutable literals on a file-by-file basis. Ruby 3.4 adds a transition for files without that comment. Their literals remain mutable, but mutation produces a deprecation warning when enabled. Ruby 2.3 release, transition design.
message = "draft"
message << " ready"
Running that file with ruby -W:deprecated on 3.4.10 prints:
warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)
The corresponding test still passes. Running it with --enable-frozen-string-literal deliberately changes the condition and raises FrozenError on both tested Rubies. Keep that experiment separate from the normal 3.4 job; it is not evidence that every 3.4 upgrade breaks string mutation.
Use +"draft" or "draft".dup when constructing a mutable buffer. An explicit # frozen_string_literal: false opts a file out while its mutation sites are reviewed. Changing the comment to true is a deliberate behavior change, not a warning-suppression fix. Ruby 4.0 still documents the default as false; a warning is not a promised switch date. Ruby 4.0 string directive.
Test values instead of their diagnostic punctuation
Ruby 3.4 changes Hash#inspect. The original discussion began with ambiguous output for unusual symbol keys and broadened into a consistent format. Design discussion. The same data now renders differently:
-{:status=>"queued", "attempt"=>1}
+{status: "queued", "attempt" => 1}
A recorded snapshot assertion fails solely because it compares that representation. Compare the hash’s values when those are the contract. When the actual requirement is a log or user-facing message, test the relevant fields or define an explicit serialization format instead of inheriting inspect formatting.
Error and backtrace display also changes. A check for undefined method `deliver_now' fails when Ruby prints undefined method 'deliver_now'; a backtrace gains the class-qualified method name and different quotation marks. The repaired tests assert NoMethodError, its name, and backtrace_locations.first.base_label. All three output-sensitive tests fail on 3.4 before the repair and pass on both runtimes afterward. Quote consistency, method-owner backtraces.
The Hash#inspect change does not itself change JSON or YAML serialization. It matters to those files only if application code stored an inspected string inside them. Avoid rewriting unrelated fixtures to accommodate a diagnostic display change.
Check the tools that read or compile Ruby
Prism became Ruby’s default parser in 3.4. Ruby’s own parser, a linter’s parser and a coverage tool are distinct dependencies; switching the interpreter does not update them all. The compatibility escape hatch ruby --parser=parse.y works in the recorded 3.4.10 probe. Use it to isolate a parser issue and report a reproduction, not to avoid checking your tooling. Ruby 3.3 shipped Prism in 2023 alongside the existing parser; 3.4 made it the default. Tools adopting its parsing or translation APIs move on their own release schedules. Ruby 3.3 parser introduction.
These are verified upstream checkpoints, not universal minimum versions. The fixture does not certify each tool against your application.
| Tool | Evidence worth checking | Application action |
|---|---|---|
| RuboCop | 1.75.0 made Prism translation the default for Ruby 3.4 analysis. | Check TargetRubyVersion, parser engine and extension cops. |
| Bootsnap | 1.24.2 and 1.24.6 address parser-selection and detection problems. | Check the Ruby patch, rebuild the extension and regenerate its cache. |
| Spring | 4.7.0 CI includes 3.4; this does not establish the earliest compatible release. | Stop an existing preloader and compare commands with and without it. |
| Pry | 0.15.2 repairs input handling when Prism detection fails. | Exercise console input and debugger integration. |
| SimpleCov | 1.2.0 retains Ruby 3.2 as its minimum. | Check actual coverage output and subprocess collation, not only a green suite. |
| Byebug | 13.0.0 includes newer Ruby compatibility and dependency fixes. | Test the installed debugger; it is incorrect to declare Byebug universally unusable. |
The new it block parameter is optional syntax. Keep explicit block parameters while both CI jobs run on 3.3 and 3.4. RSpec-core 3.13.6 explicitly accepted an unused block to quiet a separate warning. It was not an it-parameter fix, and does not establish that ordinary it "description" do examples broke. RSpec change.
Rails 7.2 defaults enable YJIT when Ruby provides its activation API. Record whether it actually starts. Our local Ruby builds differ in YJIT availability, so their timings are not a performance comparison. Defaults, activation.
Test Ruby 3.3 to 3.4 with Rails held fixed
The same generated Rails 8.0.5.1 application passed 15 tests and 47 assertions on Ruby 3.3.12 and 3.4.10. Its Rails defaults, Sprockets, Sidekiq, Redis and Devise versions stayed fixed. The local and separate Debian Bookworm container runs passed. Both runtimes compiled and served the expected CSS and processed a real Sidekiq job into the database. Selected native-library checks also passed.
The application already resolves bigdecimal and drb. Artificially requiring mutex_m fails on 3.4 because the application does not declare or use it. That result diagnoses dependency availability; it does not contradict the passing application boot and suite.
The downloadable reproduction contains the generated application, locked dependencies, before-and-after probes and replay instructions. AI assistance was used for research and execution. These runs describe a fixture, not an Allerin client deployment or a performance benchmark.
Select the runtime explicitly, including the interpreter used by child processes. Our container matrix uses:
strategy:
matrix:
ruby: ["3.3.12", "3.4.10"]
container:
image: ruby:${{ matrix.ruby }}-bookworm
In each application container, record ruby -v and bundle _2.5.22_ exec ruby -e 'puts RUBY_VERSION', then run:
RUBYOPT="-W:deprecated" bundle _2.5.22_ exec bin/rails test --seed 6600
The download supplies the complete job, asset and native-library checks. Keep the two jobs through the upgrade and rollback window; a fixed sprint length is not a correctness criterion.
Treat warnings as observations with locations and causes. The Rails suite emitted zero warning: lines on either runtime. The separate chilled-string example emitted one on 3.4, and the dependency probe emitted three migration warnings on 3.3. A zero count from this small suite cannot establish that every production path is warning-free.
Keep the operating-system change explicit too. At the source check, the official 3.4 image alias selects Trixie; 3.4.10-bookworm names a different Debian base. Choose the intended variant and record its image digest. Rebuild native extensions for the target Ruby and platform. Recheck the installed libvips package for Active Storage when the image changes; the Ruby version does not determine that package’s version. Official tag mapping, image definitions.
Ruby 3.4 also changes socket connection behavior through Happy Eyeballs, so exercise outbound HTTP, database and service connections appropriate to your application. Ruby 3.4 socket change.
What to do this week
- Record the Rails patch, Ruby versions, dependency lock and the blocker, if any, to keeping Rails fixed.
- Run the same suite and representative jobs on both Rubies with deprecation warnings enabled. Separate dependency failures, literal warnings and output-only assertions.
- Review
.ruby-version,.tool-versionswhere used, the Dockerfile’s Ruby argument or tag, and the CI matrix. Check the Gemfile Ruby constraint and lockfile’sRUBY VERSIONandPLATFORMStoo; platforms identify build targets, not the interpreter version. - Rebuild and test the deployment image, including native extensions, assets, connections and background workers. Retain the working prior image for rollback.
- Deploy the Ruby change independently once these checks pass. Start the Rails branch from that verified state.
Ruby 4.0 is a separate next hop
Released on 25 December 2025, Ruby 4.0 moved ostruct, pstore, benchmark, logger, rdoc, win32ole, irb, reline, readline and fiddle to bundled gems. Ruby 3.4.0’s warning map listed nine future candidates under the then-planned label 3.5.0; readline was explicitly excluded from warnings. Ruby 3.4.10 updates those labels to 4.0.0; readline remains excluded. The warning map and the eventual release list are different records. 3.4.0 warning map, 3.4.10 map, 4.0 release.
ZJIT is opt-in, and the 4.0 release announcement cautions against production deployment of that new compiler. This fixture did not test Ruby 4.0. A permissive gemspec or selected CI cell cannot certify an application for that next hop.
For an application-specific sequence, use Allerin’s Rails upgrade guide.
Runtime versions, commands, expected failures and limitations are included in the downloadable reproduction. Facts checked 7 September 2026.
Primary sources
- Ruby branches
- release list
- Rails 6.1.7.10 gemspec
- Rails 7.0.10 gemspec
- Rails 7.1.6 gemspec
- Rails 7.2.3.2 gemspec
- Rails 8.0.5.1 gemspec
- Rails 8.1.3.1 gemspec
- pipeline
- selection rules
- Ruby 2.5’s 2017 release
- Ruby 3.4 library changes
- 7.1 changelog
- 7.0.9 changelog
- Dependency change
- 7.0.9 source
- 7.1.0 source
- Ruby 2.3 release
- transition design
- Ruby 4.0 string directive
- Design discussion
- Quote consistency
- method-owner backtraces
- Ruby 3.3 parser introduction
- RuboCop changelog
- Bootsnap changelog
- 4.7.0 CI
- Pry changelog
- SimpleCov changelog
- Byebug changelog
- RSpec change
- Defaults
- activation
- Official tag mapping
- image definitions
- Ruby 3.4 socket change
- 3.4.0 warning map
- 3.4.10 map
- 4.0 release