You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Ruby 4.0 was released 2025-12-25; latest patch as of now is 4.0.6 (2026-07-14). Ruby 4.0 bundles RubyGems/Bundler 4.x and minitest 6.0.0.
Current repo state (branch rails-8.1-upgrade-1325):
.ruby-version = 3.4.7
Gemfile: no explicit ruby '...' pin; rails '~> 8.1.0'; minitest '~> 5.1' (comment: "minitest 6 is incompatible with rails 7.2.x" - stale, app is now on Rails 8.1)
Gemfile.lock: BUNDLED WITH 2.6.9; minitest resolved to 5.27.0
No GitHub Actions/CI workflows, no Dockerfile in this repo
app-scripts/release_and_build.sh reads .ruby-version dynamically (no hardcoded version pin) - safe
config/boot.rb has a Ruby-3.4-specific bootsnap workaround for parallel test workers (frozen array issue) - re-verify need on Ruby 4
config/application.rb sets config.yjit = true (Ruby 3.3+) - should keep working
Ruby 4.0 breaking/notable changes relevant to this app
(researched via ruby-lang.org release notes + rubyreferences.github.io/rubychanges/4.0.html)
Set reimplemented as a core C class (was autoloaded stdlib). No custom Set subclasses or @hash-ivar reliance found in this codebase (grep clean). Low risk.
Set#to_set/Enumerable#to_set with extra arguments deprecated - no usages found in app/lib/app-scripts (grep clean).
cgi stdlib mostly removed; only cgi/escape remains (CGI.escape(HTML)/unescape(HTML)/escapeURIComponent/escapeElement). App uses CGI.escapeHTML in app/models/master.rb only - compatible, but must verify it's actually loaded (no explicit require 'cgi' found; likely pulled in transitively via Rack/ActionDispatch - verify at boot).
Several stdlibs moved from default gems to bundled gems (must be explicit Gemfile deps if directly used): benchmark, fiddle, irb, logger, ostruct, pstore, rdoc, readline, reline, win32ole. No direct usage found in app code (grep clean) - but transitive gem dependencies (aws-sdk, redcap, delayed_job, etc.) may need these; must verify via bundle install + boot + full test run rather than static analysis alone.
sorted_set fully removed (was already deprecated) - not used (grep clean).
*nil splat semantics changed (no longer calls .to_a) - unlikely to affect app code; no fragile patterns found.
Net::HTTP no longer auto-sets Content-Type: application/x-www-form-urlencoded on POST/PUT bodies when unset - no direct Net::HTTP POST usage with implicit content-type found in app/lib/app-scripts (grep clean); AWS SDK / other gems' internal usage is out of our control, monitor via test failures.
Backtrace formatting changes (<internal:> frames removed, ArgumentError shows class name + code snippet) - could affect any spec/test that asserts on exact backtrace/error-message strings.
RubyGems/Bundler 4 bundled with Ruby 4.0 - current Gemfile.lockBUNDLED WITH is 2.6.9; needs bump.
Decisions
Target Ruby version: 4.0.6 (latest patch).
Bundler/RubyGems: upgrade to 4.x as part of this issue (not deferred).
minitest '~> 5.1' pin: investigate and remove if Rails 8.1 + minitest 6 works; update/remove stale comment either way.
Deploy repo sync (fphs-app-deploy / fphs-rails-app, EB platform Ruby 4.0 availability): out of scope for this issue - those are forks that will be brought into line separately, soon after.
Tag start-ruby4-upgrade-1300 on current branch tip.
Create branch ruby4-upgrade-1300 off rails-8.1-upgrade-1325.
Phase 1 - Ruby/Bundler version bump (depends on Phase 0)
Update .ruby-version to 4.0.6.
Ensure local Ruby 4.0.6 is installed.
Upgrade Bundler to 4.x, regenerate Gemfile.lockBUNDLED WITH line.
Re-run bundle install to rebuild native extension gems (nokogiri, pg, bcrypt, bootsnap, debug, msgpack, ruby_parser, sassc-embedded, ffi if present) against Ruby 4.0.6. Watch for build failures - these gems may need version bumps if their published gem versions don't yet support Ruby 4.0 ABI.
Phase 2 - Dependency/Gemfile adjustments (depends on Phase 1)
Investigate the gem 'minitest', '~> 5.1' pin: loosen/remove, bundle update minitest, run full RSpec suite. If green with minitest 6.x, remove the pin and its stale comment; otherwise keep pin but update the comment to reflect the real reason.
After bundle install, check for LoadError on any of: benchmark, fiddle, irb, logger, ostruct, pstore, rdoc, readline, reline during boot/test run - add explicit gem entries only if something breaks.
Update THIRD-PARTY-LIBRARIES.md gem list/licenses if new explicit gems were added, or if bundler version changes what's reported.
Phase 3 - Ruby 4.0 code-level compatibility checks (can run in parallel with Phase 2 once Ruby 4.0.6 is installed)
Verify CGI.escapeHTML in app/models/master.rb still resolves correctly at runtime; add explicit require 'cgi' only if it fails to autoload.
Re-test the Ruby-3.4 bootsnap workaround in config/boot.rb (parallel test worker frozen-array issue) under Ruby 4.0.6 - remove if no longer needed, or keep with an updated comment.
Confirm YJIT (config.yjit = true) still boots and functions correctly under Ruby 4.0.6.
Grep specs for brittle backtrace/error-message string assertions that could break due to Ruby 4.0's backtrace formatting changes - fix any that break during Phase 4 test run.
Phase 4 - Full validation (depends on Phases 1-3)
Boot the app and check for deprecation warnings/initializer errors.
Run the full RSpec suite via app-scripts/parallel_test.sh (or app-scripts/headless_rspec.sh for system specs) - fix any failures attributable to Ruby 4.0 changes.
Run brakeman and bundler-audit against the updated lockfile.
Manually spot-check high-risk areas already flagged during the Rails 8/8.1 work: dynamic model migrations, 2FA/AR encryption, Delayed Job timing, caching.
config/application.rb - verify YJIT config still applies
app/models/master.rb - verify CGI.escapeHTML still resolves
THIRD-PARTY-LIBRARIES.md - update gem list if new explicit deps added
app-scripts/release_and_build.sh - no hardcoded version, but re-verify it still validates .ruby-version correctly during a release dry run
Verification
bundle install completes cleanly on Ruby 4.0.6 with no native-extension build failures.
app-scripts/parallel_test.sh full suite green.
app-scripts/headless_rspec.sh for representative system specs green.
App boots with no deprecation/initializer errors.
brakeman and bundler-audit clean (or no new findings vs. current baseline).
Manual boot-console check: CGI.escapeHTML('<a>'), Set.new([1,2]).to_a, confirm no LoadError for bundled-gem-now stdlibs.
Further considerations
If any native extension gem (nokogiri/pg/bcrypt/etc.) doesn't yet publish a Ruby-4.0-compatible precompiled binary, that gem's version may need bumping ahead of/alongside the Ruby bump - discover this empirically in Phase 1 step 6 rather than pre-guessing.
The minitest pin comment is stale (references Rails 7.2.x) regardless of outcome - it must be corrected either way (Phase 2 step 7).
Prior to making any changes, time a defined set of controller, request and model specs, then repeat after the upgrade is complete. Compare the changes to look for performance regressions (or improvements!)
Relies on #1015
Plan
Context
rails-8.1-upgrade-1325(PR Upgrade to Rails 8.1.3.1 #1338, targets Rails 8.1.3.1, not yet merged todevelop).rails-8.1-upgrade-1325):.ruby-version= 3.4.7Gemfile: no explicitruby '...'pin;rails '~> 8.1.0';minitest '~> 5.1'(comment: "minitest 6 is incompatible with rails 7.2.x" - stale, app is now on Rails 8.1)Gemfile.lock:BUNDLED WITH 2.6.9; minitest resolved to 5.27.0app-scripts/release_and_build.shreads.ruby-versiondynamically (no hardcoded version pin) - safeconfig/boot.rbhas a Ruby-3.4-specific bootsnap workaround for parallel test workers (frozen array issue) - re-verify need on Ruby 4config/application.rbsetsconfig.yjit = true(Ruby 3.3+) - should keep workingRuby 4.0 breaking/notable changes relevant to this app
(researched via ruby-lang.org release notes + rubyreferences.github.io/rubychanges/4.0.html)
Setreimplemented as a core C class (was autoloaded stdlib). No customSetsubclasses or@hash-ivar reliance found in this codebase (grep clean). Low risk.Set#to_set/Enumerable#to_setwith extra arguments deprecated - no usages found in app/lib/app-scripts (grep clean).cgistdlib mostly removed; onlycgi/escaperemains (CGI.escape(HTML)/unescape(HTML)/escapeURIComponent/escapeElement). App usesCGI.escapeHTMLinapp/models/master.rbonly - compatible, but must verify it's actually loaded (no explicitrequire 'cgi'found; likely pulled in transitively via Rack/ActionDispatch - verify at boot).Gemfiledeps if directly used):benchmark,fiddle,irb,logger,ostruct,pstore,rdoc,readline,reline,win32ole. No direct usage found in app code (grep clean) - but transitive gem dependencies (aws-sdk, redcap, delayed_job, etc.) may need these; must verify viabundle install+ boot + full test run rather than static analysis alone.sorted_setfully removed (was already deprecated) - not used (grep clean).*nilsplat semantics changed (no longer calls.to_a) - unlikely to affect app code; no fragile patterns found.Content-Type: application/x-www-form-urlencodedon POST/PUT bodies when unset - no directNet::HTTPPOST usage with implicit content-type found in app/lib/app-scripts (grep clean); AWS SDK / other gems' internal usage is out of our control, monitor via test failures.<internal:>frames removed,ArgumentErrorshows class name + code snippet) - could affect any spec/test that asserts on exact backtrace/error-message strings.Gemfile.lockBUNDLED WITHis 2.6.9; needs bump.Decisions
minitest '~> 5.1'pin: investigate and remove if Rails 8.1 + minitest 6 works; update/remove stale comment either way.fphs-app-deploy/fphs-rails-app, EB platform Ruby 4.0 availability): out of scope for this issue - those are forks that will be brought into line separately, soon after.rails-8.1-upgrade-1325now (coordinated stacked branches, as done for Rails 8 upgrade prep: fix otp_enc_key, advance load_defaults to 7.2 #1299 -> Upgrade Rails to 8.0.5 and Puma to 8.0.2 - fixes #1015 #1305). Rebase ontodeveloplater once the Rails 8.1 PR chain merges, before opening the PR.Steps
Phase 0 - Branch setup
start-ruby4-upgrade-1300on current branch tip.ruby4-upgrade-1300offrails-8.1-upgrade-1325.Phase 1 - Ruby/Bundler version bump (depends on Phase 0)
.ruby-versionto4.0.6.Gemfile.lockBUNDLED WITHline.bundle installto rebuild native extension gems (nokogiri, pg, bcrypt, bootsnap, debug, msgpack, ruby_parser, sassc-embedded, ffi if present) against Ruby 4.0.6. Watch for build failures - these gems may need version bumps if their published gem versions don't yet support Ruby 4.0 ABI.Phase 2 - Dependency/Gemfile adjustments (depends on Phase 1)
gem 'minitest', '~> 5.1'pin: loosen/remove,bundle update minitest, run full RSpec suite. If green with minitest 6.x, remove the pin and its stale comment; otherwise keep pin but update the comment to reflect the real reason.bundle install, check forLoadErroron any of:benchmark,fiddle,irb,logger,ostruct,pstore,rdoc,readline,relineduring boot/test run - add explicitgementries only if something breaks.THIRD-PARTY-LIBRARIES.mdgem list/licenses if new explicit gems were added, or if bundler version changes what's reported.Phase 3 - Ruby 4.0 code-level compatibility checks (can run in parallel with Phase 2 once Ruby 4.0.6 is installed)
CGI.escapeHTMLinapp/models/master.rbstill resolves correctly at runtime; add explicitrequire 'cgi'only if it fails to autoload.config/boot.rb(parallel test worker frozen-array issue) under Ruby 4.0.6 - remove if no longer needed, or keep with an updated comment.config.yjit = true) still boots and functions correctly under Ruby 4.0.6.Phase 4 - Full validation (depends on Phases 1-3)
app-scripts/parallel_test.sh(orapp-scripts/headless_rspec.shfor system specs) - fix any failures attributable to Ruby 4.0 changes.brakemanandbundler-auditagainst the updated lockfile.Phase 5 - PR
developyet (the Rails 8.1 chain - Rails 8 upgrade prep: fix otp_enc_key, advance load_defaults to 7.2 #1299/Upgrade Rails to 8.0.5 and Puma to 8.0.2 - fixes #1015 #1305/Upgrade to Rails 8.1.3.1 #1338 - hasn't merged). Pushruby4-upgrade-1300and open a PR with base = the Rails 8.1 upgrade branch (mirroring the Rails 8 upgrade prep: fix otp_enc_key, advance load_defaults to 7.2 #1299 -> Upgrade Rails to 8.0.5 and Puma to 8.0.2 - fixes #1015 #1305 stacked pattern), referencing "stacked on rails-8.1-upgrade-1325 (Upgrade to Rails 8 #1015/Upgrade to Rails 8.1 (checkpoint after Rails 8.0 in #1015) #1325/Upgrade to Rails 8.1.3.1 #1338), fixes Upgrade to Ruby 4 #1300". Document the merge order dependency clearly in the PR description.Relevant files
.ruby-version- bump to 4.0.6Gemfile- minitest pin investigation, possible new explicit gem entriesGemfile.lock- regenerateBUNDLED WITH(Bundler 4.x) and all resolved gem versionsconfig/boot.rb- re-verify/remove Ruby 3.4 bootsnap workaroundconfig/application.rb- verify YJIT config still appliesapp/models/master.rb- verifyCGI.escapeHTMLstill resolvesTHIRD-PARTY-LIBRARIES.md- update gem list if new explicit deps addedapp-scripts/release_and_build.sh- no hardcoded version, but re-verify it still validates.ruby-versioncorrectly during a release dry runVerification
bundle installcompletes cleanly on Ruby 4.0.6 with no native-extension build failures.app-scripts/parallel_test.shfull suite green.app-scripts/headless_rspec.shfor representative system specs green.brakemanandbundler-auditclean (or no new findings vs. current baseline).CGI.escapeHTML('<a>'),Set.new([1,2]).to_a, confirm noLoadErrorfor bundled-gem-now stdlibs.Further considerations