Skip to content

Upgrade Rails 7.2.3.1 → 8.0.5#2017

Open
jbirdjavi wants to merge 10 commits into
masterfrom
rails-8.0-upgrade
Open

Upgrade Rails 7.2.3.1 → 8.0.5#2017
jbirdjavi wants to merge 10 commits into
masterfrom
rails-8.0-upgrade

Conversation

@jbirdjavi

@jbirdjavi jbirdjavi commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

What this is

Rails 7.2.3.1 → 8.0.5 with config.load_defaults flipped to 8.0. The 8.0 defaults change exactly three settings, all class (a) for this app:

  • to_time_preserves_timezone = :zone
  • strict_freshness = true (freshness checked via ETag before Last-Modified)
  • Regexp.timeout ||= 1 (process-global; ||= respects any explicit app setting)

No app-code deprecations existed before or after the flip; no code changes were required beyond config merge-back.

Commit-by-commit

  1. Upgrade Rails 7.2.3.1 → 8.0.5bundle update --conservative rails; lockfile churn is rails-family only. cgi stays in the lock via datadog 2.35.0's runtime dep (Rails 8 dropped its own cgi dependency — expected, don't revert).
  2. Security: bump vulnerable gems to clear bundle-audit — see gem list below.
  3. Bump brakeman 8.0.4 → 8.0.5 — ruby.yml runs brakeman with --ensure-latest, which exits 5 on 8.0.4; with Rails 8.0.5 the EOLRails warning also clears (scan: 0 warnings).
  4. Mirror the Rails 8.0 skeleton Gemfile layout — pure restructure, Gemfile.lock byte-identical (details under Adoptions).
  5. Raw output of bin/rails app:update --force — unreviewed generator output committed verbatim so the merge-back is reviewable as its own diff.
  6. Merge custom config into new 8.0 defaults; adopt config.load_defaults 8.0 — restores the Ougai logger, :redis_cache_store, AMS json_api adapter, Rack::Cors, custom test-env stores; comments out the skeleton's production logger/log_tags (Ougai override landmine) and the mailer example.com default_url_options; keeps the 8.0 additions to filter_parameters (:cvv/:cvc) without re-adding :email; introduces the health-path constants (below).
  7. Skeleton-diff cleanup beyond app:update.dockerignore mirrored (keeps /.env* excluded with a why-note), bin/docker-entrypoint mirrored (inert parity per the standing fleet ruling), stock permissions_policy.rb deleted (8.0 skeleton drops it), lib/assets/.keepscript/.keep.
  8. Remove obsolete EOLRails brakeman.ignore entry — the stale Rails 7.1 EOL suppression no longer matches anything on 8.0.5.
  9. Regenerate schema.rb under the Rails 8.0 dumper — expected mechanical diff only (see Out of scope).
  10. Docs: fix stale framework version claims — CLAUDE.md said Rails 7.1; README claimed Ruby 2.6.6 / Bundler 1.17.3 / Rails 5.2.3. Now 3.3.10 / 2.7.2 / 8.0.5. (No docs/DECISION_LOG.md in this repo.)

Security commit (lockfile-only, conservative in-constraint)

All 10 gems flagged by the raw advisory DB (48 advisories):

concurrent-ruby 1.3.6→1.3.8, crass 1.0.6→1.0.7, faraday 2.14.2→2.14.3, json 2.19.8→2.21.1, loofah 2.25.1→2.25.2, msgpack 1.8.1→1.8.3, net-imap 0.6.4→0.6.4.1, nokogiri 1.19.3→1.19.4 (27 advisories), rails-html-sanitizer 1.7.0→1.7.1, websocket-driver 0.8.0→0.8.2.

Raw bundle exec bundle audit check --update now reports 0 vulnerabilities with no --ignore (the org-level CVE_IGNORES entries become no-ops for this repo). Expected churn notes: the brakeman 8.0.5 bump exists solely for the --ensure-latest CI gate; advisory-DB churn is a treadmill — new advisories published after this PR are new work, not regressions of this commit.

Health-path constant extraction

MobileContentApi::HEALTHCHECK_PATH / HEALTHCHECK_ACTION defined in config/application.rb above the Application class (fleet merge-back convention), consumed at all 4 sites:

  1. config/environments/production.rbssl_options redirect exclude + silence_healthcheck_path
  2. config/routes.rb — via HEALTHCHECK_PATH.delete_prefix("/")
  3. config/initializers/lograge.rbignore_actions
  4. config/initializers/ddtrace.rb — the Datadog span-drop filter (a rename without this site would silently flood Datadog with healthcheck spans)

Adoptions (new-app 8.0 tech)

  • Thruster: adopt, as a stacked PR to followAdopt Thruster in front of Puma #2018 (standard fleet pattern: gem + bin/thrust + Dockerfile CMD/EXPOSE, then nginx sidecar removal in cru-terraform after deploy).
  • Propshaft: inapplicable — this API app has no asset pipeline at all (sprockets was already absent); the Gemfile carries a commented propshaft note in place of the stale sprockets-rails comment. No propshaft PR will exist for this app.
  • solid_cache / solid_queue / solid_cable: declined — Redis cache + Sidekiq Pro + redis Action Cable are deliberate choices; commented placeholders with why-notes sit in the Gemfile per the skeleton-mirroring convention.
  • Kamal: declined — AWS ECS deploy; commented placeholder in the Gemfile.

Bot-PR accounting

Out of scope (noted, not changed here)

  • Tracked .env — this repo commits a real .env (fleet-wide pattern; okta/mpd_tool/autonag likewise). .dockerignore /.env* keeps it out of the image. Rotation + untracking is a dedicated fleet remediation sweep, not this PR.
  • Dockerfile harvest candidates from the 8.0 skeleton (deliberately-divergent file, never restructured): BuildKit # check=error=true directive, gem-cache layer cleanup (rm -rf ~/.bundle "$BUNDLE_PATH"/ruby/*/cache), build-time bootsnap precompile.
  • schema.rb re-dump — expected, mechanical, one-time: header [7.1][8.0] plus the 8.0 dumper's pg_catalog.plpgsql extension qualification. Round-trip proven (schema:load on a scratch DB re-dumps byte-identical). No table/column/index changes.

Verification (local, CI-verbatim)

  • RSpec (CI=true RAILS_ENV=test): 670 examples, 0 failures, 3 pending (same as master)
  • bundle-audit raw (fresh DB): 0; brakeman -A -q --ensure-latest: exit 0, 0 warnings, no obsolete ignores; standardrb: 369 files, no offenses
  • zeitwerk:check clean; test + production eager-load boots clean (production boot with dummy env exercised the real :redis_cache_store against Redis — write/read round-trip OK)
  • Test server boot: /monitors/lb → 200, /systems → 200 (real JSON:API payload); zero deprecation warnings in any output

Post-deploy verification (staging, by real request)

  • SSL trio / ssl_options: assume_ssl + force_ssl are enabled with the /monitors/lb exclude — verify redirect/HSTS behavior and that the health check stays exempt by real request in staging after deploy.
  • redis_cache_store on real Redis: verified locally at boot, but confirm cache reads/writes against the real ElastiCache endpoint by real request in staging after deploy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX

jbirdjavi and others added 10 commits July 21, 2026 11:41
bundle update --conservative rails; lockfile churn is rails-family only.
cgi remains in the lock via datadog 2.35.0's runtime dep (Rails 8 dropped
its own cgi dependency).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
Conservative in-constraint bumps for all 10 gems flagged by the raw
advisory DB (48 advisories): concurrent-ruby 1.3.6->1.3.8,
crass 1.0.6->1.0.7, faraday 2.14.2->2.14.3, json 2.19.8->2.21.1,
loofah 2.25.1->2.25.2, msgpack 1.8.1->1.8.3, net-imap 0.6.4->0.6.4.1,
nokogiri 1.19.3->1.19.4 (27 advisories), rails-html-sanitizer
1.7.0->1.7.1, websocket-driver 0.8.0->0.8.2. Lockfile-only; raw
`bundle audit check --update` now reports 0 with no --ignore.
Supersedes renovate #2014 (nokogiri) and dependabot #2006 (net-imap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
ruby.yml runs brakeman with --ensure-latest, which exits 5 on 8.0.4.
With Rails 8.0.5 the EOLRails warning also clears: scan is now
0 warnings. Supersedes renovate #2011.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
Skeleton-mirroring convention: propshaft declined-default comment
replaces the stale sprockets-rails comment (no asset pipeline in this
API app), solid_cache/solid_queue/solid_cable + kamal added as
commented placeholders with why-notes, thruster placeholder commented
(activated by the stacked thruster PR), sqlite3 commented example
updated to the 8.0 ">= 2.1" floor, stale kredis leftover deleted,
redis moved to the custom section with a why-note (skeleton dropped
it; still used for cache + Action Cable), custom section alphabetized
(pg folded in, google-apis before googleauth). Pure restructure:
Gemfile.lock is byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
Unreviewed generator output, committed verbatim; the following commit
restores custom configuration on top of the new defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
… 8.0

Merge-back on top of the raw app:update output:
- application.rb: restore frozen_string_literal, fileutils require, Ougai
  Log::Logger, redis_conf + :redis_cache_store, AMS json_api adapter,
  pages mkdir; add HEALTHCHECK_PATH/HEALTHCHECK_ACTION constants above
  the Application class; flip load_defaults 7.2 -> 8.0 (all three 8.0
  changes are class (a) for this app).
- production.rb: restore public_file_server.enabled = false (nginx
  serves; skeleton far-future headers line kept inert for the thruster
  PR), active_storage :amazon, secret_key_base/Action Cable forgery,
  Rack::Cors, routes default_url_options; comment out skeleton
  logger/log_tags (Ougai override landmine) and the mailer
  default_url_options example.com host (no real mailers); point
  ssl_options exclude + silence_healthcheck_path at the real
  /monitors/lb health route via the new constants.
- routes.rb, lograge.rb, ddtrace.rb: consume the health-check constants
  (routes via delete_prefix("/"); ddtrace span-drop filter included).
- development.rb: keep 8.0 caching-block restructure; restore
  Rack::Cors, log_level :debug, routes default_url_options.
- test.rb: restore custom :memory_store (skeleton is :null_store),
  active_job :test adapter, localhost default_url_options.
- filter_parameter_logging.rb: keep 8.0 additions :cvv/:cvc, do NOT
  re-add :email (deliberately unfiltered here; noted in-file).
- .gitignore: apply the 8.0 env-file hunk (no tracked .env*.erb; the
  image gets config via build ARGs, .dockerignore excludes /.env*).
- Delete stale new_framework_defaults_7_2.rb and the generated
  new_framework_defaults_8_0.rb after the flip.
- standardrb --fix for omakase spacing; STDOUT -> $stdout in bin/setup.

Verified by production boot: redis_cache_store effective, Ougai logger
is the sole broadcast target, ssl exclude true only for /monitors/lb,
public_file_server disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
Hunks the 8.0 skeleton changes but app:update does not touch:
- .dockerignore: mirror the 8.0 skeleton (env-file comment, drop the
  !/.env*.erb exception — no such files exist; add the inert Kamal
  section). /.env* stays excluded with a why-note: image config arrives
  via build ARGs and the tracked .env must never reach the image.
- bin/docker-entrypoint: mirror the 8.0 rewrite (jemalloc via find,
  last-two-args server detection). Inert parity per the standing fleet
  ruling — the custom Dockerfile does not wire it as ENTRYPOINT.
- config/initializers/permissions_policy.rb: deleted; the 8.0 skeleton
  drops this file and the app's copy was stock all-commented
  (untouched since the Rails 7.1 upgrade).
- lib/assets/.keep -> script/.keep: full skeleton rename (no sprockets;
  autoload_lib ignore list unchanged, matching the 8.0 skeleton).

Skipped as inapplicable or new-app scaffolding: skeleton ci.yml (app
has its own workflows), database.yml sqlite multi-db, app/assets +
assets.rb + layout hunks (no asset pipeline), app/channels deletions
(generator artifact; this app's Action Cable is real), bin/thrust +
Dockerfile CMD/EXPOSE (stacked thruster PR), solid_*/kamal (declined,
commented placeholders already in the Gemfile).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
…d by 8.0)

Brakeman flagged fingerprint d84924377155… as an obsolete ignore: it
suppressed the EOLRails warning for Rails 7.1.5.2, which no longer fires
now that the app is on 8.0.5. Same cleanup as canary #140.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
…diff)

One-time, deterministic re-dump: schema header [7.1] -> [8.0] and the 8.0
dumper's namespace-qualified pg_catalog.plpgsql extension (re-sorted
alphabetically). No table/column/index changes; round-trips via
db:schema:load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
CLAUDE.md said Rails 7.1; README still claimed Ruby 2.6.6 / Bundler
1.17.3 / Rails 5.2.3. Updated to the current Ruby 3.3.10 / Bundler 2.7.2
/ Rails 8.0.5. No docs/DECISION_LOG.md in this repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C9vz7gguLNupgNEFGtjLfX
@stage-branch-merger

Copy link
Copy Markdown

I see you added the "On Staging" label, I'll get this merged to the staging branch!

@stage-branch-merger

Copy link
Copy Markdown

Merge conflict attempting to merge this into staging. Please fix manually.

@jbirdjavi
jbirdjavi requested review from frett and knutsenm July 22, 2026 19:30

@knutsenm knutsenm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed as part of the fleet-wide Rails 8.0 stack audit. Verified against the diff:

  • This is the first fleet repo with real Action Cable (three custom channels + mount ActionCable.server => "/cable"), so the stray-deletion check mattered here — and passed: the upgrade leaves app/channels/spec/channels completely untouched (empty diff). Only deletions are the defaults file and the fully-commented permissions_policy.
  • HEALTHCHECK constants wired at all four sites (ssl_options exclude, silence_healthcheck_path, routes, lograge) plus the Datadog span filter, at the real route /monitors/lb. Ougai preserved; skeleton logger/log_tags commented with the override note.
  • connection_pool < 3.0 pin with the reason comment correctly blocks dependabot #2003/#1971 until Rails ≥ 8.1.2; jbuilder 2.15.1; redis kept with why-note (real Cable + cache). Schema re-dump mechanical; no Rails-8 route gotchas; the README staleness fix (Ruby 2.6.6/Rails 5.2.3 claims) is a nice touch.
  • Security: loofah 2.25.2 + rails-html-sanitizer 1.7.1 land here (master still carries the old ones until this merges). datadog is at 2.35.0 — that's above the CVE-2026-50276 floor (≥ 2.32.0), so no security action needed; just noting it trails the 2.38+ the rest of the fleet converged on, if you want to align while you're in the lockfile.
  • All 10 branch commits map one-to-one to the body's list — no narrative drift.

Post-deploy note: once the nginx sidecar is removed, /cable WebSockets flow through thruster — worth a staging smoke test of live-share before the terraform change merges. Branch protection here is strict: true, so #2018 will need the rebase+push after this merges (the body already says so).

🤖 Review drafted with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants