Skip to content

Drop EOL Ruby and Rails from CI - #22

Merged
Fivell merged 3 commits into
mainfrom
chore/drop-eol-ruby-rails
Oct 1, 2026
Merged

Fivell merged 3 commits into
mainfrom
chore/drop-eol-ruby-rails

Conversation

@Fivell

@Fivell Fivell commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

What

Drops end-of-life Ruby and Rails from CI. The Ruby versions live in the workflow
matrix; the Rails version is pinned inside each gemfile under gemfiles/, so the
substantive part of this diff is there.

Source of truth for EOL dates: endoflife.date/ruby,
endoflife.date/rails.

Ruby matrix — .github/workflows/ci.yml

before after
ruby ['3.2', '3.3', '3.4'] ['3.3', '3.4', '4.0']
  • 3.2 — EOL 2026-03-31, removed.
  • 3.3 — security-only until 2027-03-31, kept as the floor.
  • 3.4 — EOL 2028-03-31, kept.
  • 4.0 — EOL 2029-03-31, added.

No exclude: rules were added — every Ruby runs against both gemfiles. The
gemfile dimension and the four per-dummy-app steps are unchanged.

Rails pins — gemfiles/

gemfile before after
gemfiles/activeadmin_3.5.gemfile rails "~> 7.2.0" rails "~> 8.0.0"
gemfiles/activeadmin_4.0.gemfile rails "~> 8.0.0" rails "~> 8.0.0" (unchanged — see blocker)

Rails 7.2 went EOL on 2026-08-09. ActiveAdmin 3.5.2 resolves and passes
against Rails 8.0 together with the existing sprockets-rails / sassc-rails
asset stack and omniauth_openid_connect ~> 0.6.0 — verified, see below.

Pins stay three-segment on purpose. A two-segment ~> 8.0 means
>= 8.0, < 9.0 and would silently resolve to 8.1, so the leg would stop testing
the version it claims to test.

⚠️ Blocker: Rails 8.1 is not usable yet, and Rails 8.0 goes EOL 2026-11-07

This PR leaves the repo with no long-lived Rails leg. Both gemfiles now sit on
Rails 8.0, which reaches EOL on 2026-11-07 — about five weeks out. The intent
was to move gemfiles/activeadmin_4.0.gemfile to ~> 8.1.0 (EOL 2027-10-10), but
Rails 8.1 currently cannot load this gem at all.

Root cause

railties 8.1 added delegate_missing_to :@collection to
Rails::Initializable::Collection, but rails/initializable.rb only
requires "tsort" — it never requires the ActiveSupport core ext that defines
delegate_missing_to. And rails/railtie.rb requires rails/initializable
before any active_support/core_ext. So a bare require "rails/railtie" blows
up on Rails 8.1:

NoMethodError:
  undefined method 'delegate_missing_to' for class Rails::Initializable::Collection
# railties-8.1.4/lib/rails/initializable.rb:41:in '<class:Collection>'
# railties-8.1.4/lib/rails/railtie.rb:3:in '<top (required)>'
# ./lib/activeadmin-oidc.rb:21:in '<top (required)>'
# ./spec/spec_helper.rb:5:in '<top (required)>'

lib/activeadmin-oidc.rb:21 does exactly that require "rails/railtie",
deliberately and with a comment, so that
omniauth/rails_csrf_protection's railtie loads for host apps.

This is not an ActiveAdmin 4.0 / Propshaft / Tailwind problem. Minimal repro,
no ActiveAdmin in the picture:

# Rails 8.1.4 bundle
$ bundle exec ruby -e 'require "rails/railtie"'
NoMethodError: undefined method 'delegate_missing_to' for class Rails::Initializable::Collection

# same bundle, ActiveSupport core ext required first -> loads fine
$ bundle exec ruby -e 'require "active_support/core_ext/module/delegation"; require "rails/railtie"'
(no error)

# Rails 8.0.5.1 bundle -> loads fine
$ bundle exec ruby -e 'require "rails/railtie"'
(no error)

railties 8.0.5.1's initializable.rb does not use delegate_missing_to at all,
which is why 8.0 is unaffected.

Verified fix, deliberately not included here

Adding one line to lib/activeadmin-oidc.rb before the rails/railtie require:

require "active_support/core_ext/module/delegation"

makes the entire suite green on Rails 8.1.4 + ActiveAdmin 4.0.0.beta23 +
Propshaft/Tailwind
(161 / 7 / 8 / 6 examples, 0 failures). I measured this and
then reverted it: this PR is scoped to dropping EOL versions, and that line is a
change to shipped library load order, not to CI. It wants its own PR — and it is
worth noting that this is a live bug for consumers, not just a CI issue: the
gemspec allows rails >= 7.2, < 9, so any host app on Rails 8.1 that requires
this gem crashes today.

Recommended follow-up, in order:

  1. Add that require to lib/activeadmin-oidc.rb (fixes real Rails 8.1 consumers).
  2. Then move gemfiles/activeadmin_4.0.gemfile to rails "~> 8.1.0" and drop the
    comment this PR added above the pin.

Until then gemfiles/activeadmin_4.0.gemfile carries a comment naming the blocker.

gemspec — activeadmin-oidc.gemspec

before after
required_ruby_version ">= 3.2.0" ">= 3.3.0"

Everything else is untouched on purpose:

  • rails (>= 7.2, < 9) and activeadmin (>= 3.5, < 5) runtime constraints are
    unchanged. Dropping a version from CI is not evidence the gem stopped
    working on it, and raising a runtime floor is a breaking change for consumers.
  • The gem version is not bumped and nothing is released.

Is the json < 3 pin still needed?

Yes — both pins and their comments are kept, unchanged.

json 3.0 removed the quirks_mode keyword that ActiveSupport::JSON passes to
JSON.parse / JSON.generate. I checked the actual gem sources:

activesupport quirks_mode passed?
7.2.3 yes — json/decoding.rb:23, json/encoding.rb:110
8.0.5.1 yes — json/decoding.rb:25, json/encoding.rb:110
8.1.4 no — no occurrence anywhere in lib/

So the pin is fixed as of Rails 8.1 — but since the Rails 8.1 blocker above
keeps both gemfiles on Rails 8.0, every CI leg still runs activesupport 8.0,
which still passes quirks_mode. Removing the pin now would break the suite.
It can be dropped from gemfiles/activeadmin_4.0.gemfile as part of the Rails 8.1
follow-up.

Other files checked, no change needed

  • .ruby-version — 3.3.10, already at/above the new >= 3.3.0 floor.
  • .rubocop.yml — does not exist, so there is no TargetRubyVersion to keep in
    sync with required_ruby_version, and RuboCop is not a CI step in this repo.
  • README.md — no supported-versions table. Its one version mention ("Compatible
    with Rails 7.2+ and Rails 8's lazy route loading") describes the runtime
    support range, which this PR does not change.
  • No CHANGELOG in this repo, so none was created.

Verification

Real local runs, every combination below. CI=true set throughout, each gemfile
in its own BUNDLE_PATH, all four dummy-app suites run separately exactly as CI
invokes them (rake spec, rake spec:engine, rake spec:isolated,
rake spec:root).

CI=true BUNDLE_GEMFILE=gemfiles/<gemfile> RBENV_VERSION=<ruby> rbenv exec bundle install
CI=true BUNDLE_GEMFILE=gemfiles/<gemfile> RBENV_VERSION=<ruby> rbenv exec bundle exec rake <suite>

All six combinations green, at the random seed each run picked for itself:

ruby gemfile rake spec spec:engine spec:isolated spec:root
3.4.10 activeadmin_3.5 161 examples, 0 failures, 1 pending 7 examples, 0 failures 8 examples, 0 failures 6 examples, 0 failures
3.4.10 activeadmin_4.0 161 examples, 0 failures, 8 pending 7 examples, 0 failures 8 examples, 0 failures 6 examples, 0 failures
4.0.6 activeadmin_3.5 161 examples, 0 failures, 1 pending 7 examples, 0 failures 8 examples, 0 failures 6 examples, 0 failures
4.0.6 activeadmin_4.0 161 examples, 0 failures, 8 pending 7 examples, 0 failures 8 examples, 0 failures 6 examples, 0 failures
3.3.12 activeadmin_3.5 161 examples, 0 failures, 1 pending * 7 examples, 0 failures 8 examples, 0 failures 6 examples, 0 failures
3.3.12 activeadmin_4.0 161 examples, 0 failures, 8 pending * 7 examples, 0 failures 8 examples, 0 failures 6 examples, 0 failures

bundle install exited 0 for all six.

* The two Ruby 3.3.12 rake spec runs initially came back with 2 and 1 failures
respectively. That is a pre-existing, seed-dependent order dependency, not a
Ruby 3.3 problem
— see the next section. The numbers above are from re-running
those two legs at --seed 1.

Resolved versions were identical across Rubies:

  • activeadmin_3.5.gemfile → activeadmin 3.5.2, rails 8.0.5.1, sprockets-rails
    3.5.2, sassc-rails 2.1.2, omniauth_openid_connect 0.6.1, devise 5.0.4, json 2.21.2
  • activeadmin_4.0.gemfile → activeadmin 4.0.0.beta23, rails 8.0.5.1, propshaft
    1.3.2, tailwindcss-rails 4.6.0, omniauth_openid_connect 0.8.0, devise 5.0.4, json 2.21.2

For reference, the pre-change baseline on Ruby 3.4.10 was also green
(activeadmin_3.5.gemfile on Rails 7.2.3, activeadmin_4.0.gemfile on Rails
8.0.5.1), so these numbers are a like-for-like comparison and not a suite that was
already red.

⚠️ Pre-existing order-dependent flake in rake spec on Rails 8 (~1 seed in 10)

spec/spec_helper.rb sets config.order = :random with a fresh seed per run, and
the default suite has an order dependency that only manifests on Rails 8. Two
examples are involved:

  • spec/unit/configuration_spec.rb:204 — "#login_submit_path points at the
    OmniAuth entry point when stub login is off"

    expected: "/oidc"
         got: "/admin/auth/oidc"
    

    The expectation is built from the global OmniAuth.config.path_prefix,
    while login_submit_path is derived from the gem's own
    omniauth_path_prefix (lib/activeadmin/oidc/configuration.rb:193). As
    lib/activeadmin/oidc/engine.rb:95 already notes, Devise overwrites that
    OmniAuth global at route-draw time — so under Rails 8 lazy route loading
    the value this unit spec reads depends on whether some earlier request spec
    has already triggered a route draw. Pure order dependency.

  • spec/requests/stub_login_spec.rb:229 — "404s when the flag is flipped off
    after the route was drawn"
    — gets a raised ActionController::RoutingError
    instead of a 404, again depending on route-draw timing.

Evidence that this is pre-existing and not introduced here — all on Ruby 3.4.10:

tree gemfile Rails seed 6 seed 21181
pristine main activeadmin_4.0 (untouched by this PR) 8.0.5.1 1 failure 2 failures
pristine main activeadmin_3.5 7.2.3 0 failures 0 failures
this branch activeadmin_3.5 8.0.5.1 1 failure 2 failures
this branch activeadmin_4.0 8.0.5.1 1 failure 2 failures

And the failure tracks the seed, not the Ruby — same gemfile, same bundle:

seed ruby 3.3.12 ruby 3.4.10 ruby 4.0.6
21181 2 failures 2 failures 2 failures
35323 0 failures 0 failures 0 failures

A sweep of seeds 1–10 on this branch failed on seed 6 only, for both gemfiles
identically — roughly a 10% chance per run.

So: main's ActiveAdmin 4.0 legs are already intermittently red today, because
they already pin Rails 8.0. Rails 7.2 happened to mask the dependency on the 3.5
legs, so moving the 3.5 gemfile to Rails 8.0 widens this pre-existing exposure
from 3 CI legs to all 6.
Expect occasional red runs on this PR and on main
until the two specs are fixed. I left them alone deliberately — they are spec
hygiene, not EOL-version work, and fixing them here would bury the version diff
this PR is supposed to be reviewable as. Worth their own PR, alongside the Rails
8.1 fix above.

Ruby 3.3 patch choice

Ruby 3.3 was checked on 3.3.12, not 3.3.0: 3.3.0 is the single release with the
anonymous-parameter-forwarding-inside-a-block parser bug, which ruby/setup-ruby
never installs for ruby-version: '3.3' (it picks the newest 3.3 patch). Nothing
in this repo was changed to accommodate it.

Fivell added 3 commits October 1, 2026 13:47
Ruby 3.2 reached end of life on 2026-03-31 (endoflife.date/ruby), so CI
no longer needs to prove the gem works there. Ruby 4.0 (eol 2029-03-31)
takes its place alongside 3.3 (security-only until 2027-03-31) and 3.4.

No exclude rules: every Ruby in the matrix runs against both gemfiles.
Rails 7.2 reached end of life on 2026-08-09 (endoflife.date/rails), so
the ActiveAdmin 3.5 leg now pins Rails 8.0. ActiveAdmin 3.5.2 resolves
and passes against it with the existing sprockets-rails / sassc-rails
stack and omniauth_openid_connect 0.6.1 — all four dummy-app suites are
green on Ruby 3.4.10.

The ActiveAdmin 4.0 leg stays on Rails 8.0 for now. Rails 8.1 is not
usable yet: railties 8.1 added `delegate_missing_to :@collection` to
Rails::Initializable::Collection but rails/initializable.rb only
requires "tsort", and rails/railtie.rb requires it before any
ActiveSupport core ext. This gem requires "rails/railtie" directly
(lib/activeadmin-oidc.rb) so loading it raises

  NoMethodError: undefined method `delegate_missing_to`
                 for class Rails::Initializable::Collection

A bare `require "rails/railtie"` reproduces this with no ActiveAdmin in
the picture, and it does not happen on Rails 8.0.

Both pins stay three-segment on purpose: a two-segment `~> 8.0` means
>= 8.0, < 9.0 and would silently resolve to 8.1, so the leg would stop
testing the version it claims to.

Rails 8.0 goes EOL on 2026-11-07, so this needs a follow-up.
Ruby 3.2 went EOL on 2026-03-31 and is no longer in CI, so the gem no
longer claims to support it. Ruby 3.3 is the new floor.

The rails (>= 7.2, < 9) and activeadmin (>= 3.5, < 5) runtime
constraints are deliberately untouched: dropping a Rails version from
CI is not evidence the gem stopped working on it, and raising a runtime
floor would be a breaking change for consumers.
@Fivell
Fivell merged commit 2f640aa into main Oct 1, 2026
6 checks passed
@Fivell
Fivell deleted the chore/drop-eol-ruby-rails branch October 1, 2026 12:22
Fivell added a commit that referenced this pull request Oct 1, 2026
Now that the gem loads on Rails 8.1 the AA 4.0 leg can test it. Rails
8.0 goes EOL on 2026-11-07 (endoflife.date/rails); 8.1 is supported
until 2027-10-10, so this gives the repo a long-lived Rails leg.

gemfiles/activeadmin_3.5.gemfile deliberately stays on "~> 8.0.0" so
both currently supported Rails series are still covered.

The pin stays three-segment: a two-segment "~> 8.1" would mean
>= 8.1, < 9.0 and would drift onto 8.2 once that ships, so the leg
would stop testing the version it names.

Note on merge order: PR #22 adds a comment above this pin naming the
Rails 8.1 blocker that this branch fixes. This branch is cut from main,
where that comment does not exist yet. If #22 merges first, rebase and
delete that now-stale comment.
@senid231 senid231 mentioned this pull request Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant