Skip to content

ci: fix pre-existing failures from json 3.x and RuboCop 1.91 - #405

Merged
johnnagro merged 2 commits into
masterfrom
ci/fix-json3-and-rubocop
Sep 21, 2026
Merged

johnnagro merged 2 commits into
masterfrom
ci/fix-json3-and-rubocop

Conversation

@johnnagro

@johnnagro johnnagro commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

CI is red on every open PR right now, including #401. The failures are pre-existing and environmental — none of them are caused by the diffs under review.

master's last CI run was 2026-07-07, before json 3.0.0 shipped (2026-09-07), so nothing re-ran on master and the breakage went unnoticed.

Evidence that it isn't the PRs: on an unmodified master working tree, changing only the json version flips the suite.

json=3.0.2   133 examples, 6 failures
json=2.21.2  133 examples, 0 failures

Corroborating: the only CI legs that still pass are the Ruby 2.6 ones. json 3.x requires Ruby >= 2.7, so those resolve json 2.7.6 and go green — same ActiveSupport as the failing legs, json the only variable.

The five causes

Of the 89 failing jobs on #401, 16 were head/rails_edge legs that are already continue-on-error and don't fail the run. The remaining 73 blocking jobs break down as causes 1–4 below; cause 5 was uncovered only once those were fixed.

1. json 3.x + ActiveSupport <= 8.0 — 67 jobs

json 3.0 promoted unknown options from silently ignored to a fatal ArgumentError. ActiveSupport <= 8.0 still passes quirks_mode (active_support/json/encoding.rb:110 and decoding.rb:25), so any to_json raises:

ArgumentError: unknown keyword: quirks_mode

This surfaced through two test-only formatter lambdas:

Lograge.formatter = ->(data) { "My test: #{data.to_json}" }

Lograge's own formatters already use JSON.dump and were never affected — I ran all ten built-ins against json 3.0.x and they all pass. Switching the lambdas to JSON.dump makes the suite independent of the ActiveSupport encoder.

Rails has fixed this on 8-0-stable, 8-1-stable and main (#55534, 2786de2, #58601), but no released version carries the fix yet — 8.0.5.1 and 8.1.3.1 are both still affected. This is also the root cause of #404.

2. RuboCop 1.91's new Style/DirectiveScope cop — 6 jobs

All on rails_8.1, where rspec passes 133/0 (AS 8.1 has the encode fix) and the lint step is therefore reached. NewCops: enable plus an unbounded rubocop dependency means new cops activate the moment they ship.

Worth flagging: the cop's autocorrect rewrites the disable/enable pairs to # rubocop:disable-next, a directive RuboCop < 1.90 does not understand. I tested it — 1.80 then reports 4 offenses, so autocorrecting would have broken the older-Ruby legs instead. Moving both suppressions into .rubocop.yml is understood identically by every version in the matrix.

I also restated the gemspec exclusion for Metrics/BlockLength: a local Exclude replaces RuboCop's default list rather than extending it, so the gemspec lost its default exemption and sits right at the 25-line limit. Any PR adding a line to the gemspec trips it — which is exactly what #401 hit and worked around locally.

Note this step is currently masked almost everywhere: the other legs die at rake before reaching rubocop. Fixing only the json issue would have surfaced this across the whole matrix, so both fixes are required together.

3. ci (3.2, rails_edge) — 1 job

Rails edge now requires Ruby >= 3.3.1, so the leg cannot resolve:

Because every version of activerecord depends on Ruby >= 3.3.1
  and rails_edge.gemfile depends on activerecord >= 0, ...
version solving has failed.

4. ruby-head — benchmark

Ruby 4.1 extracted benchmark from the default gems, so ActiveSupport fails to load with cannot load such file -- benchmark. Declared explicitly, alongside the existing base64/bigdecimal/mutex_m entries — same family of breakage.

5. RuboCop crashes on TruffleRuby — 9 jobs

Fixing the spec failures exposed this one. It was previously visible on only the single leg where specs already passed (truffleruby/rails_8.1); with specs green everywhere it failed all nine TruffleRuby legs, which are not continue-on-error.

53 files inspected, no offenses detected
5 errors occurred:
An error occurred while Lint/MissingCopEnableDirective cop was inspecting ...
Errors are usually caused by RuboCop bugs.

All five are in cops that parse inline rubocop: directives, and RuboCop reports them as its own bug rather than as an offense in this code.

Rather than work around that, this stops running the linter across the matrix. Two problems were stacked:

  • rake already runs spec + rubocop, and the workflow then ran bundle exec rubocop again — every leg linted twice.
  • RuboCop resolves to a different version on each Ruby (1.50.2 on 2.6 through 1.91 on 3.4), so lint results depended on which leg ran. That is precisely what broke the matrix when 1.91 shipped Style/DirectiveScope.

The matrix now runs rake spec, and a single lint job runs RuboCop once on one Ruby. Linting is platform-independent, so this loses no coverage while making results deterministic and dropping ~180 redundant RuboCop runs. The default rake task is unchanged for local development.

Verification

CI on this branch is green — run conclusion success, 83 jobs passing. The 9 remaining failures are all head/rails_edge legs that are already continue-on-error and do not fail the run (for comparison, the last green run on master had 16 such failures).

Locally as well:

  • rake (specs + lint) passes against both json 3.0.2 and 2.21.2
  • RuboCop clean on 1.50.2, 1.56.4, 1.60, 1.80, 1.90, 1.91 — covering what the matrix resolved from Ruby 2.6 through head (1.50.2 is what the Ruby 2.6 legs got)
  • Gemspec still loads

Worth a follow-up

NewCops: enable combined with an unbounded rubocop development dependency means any newly shipped cop breaks CI the day it lands — that is the standing root cause of cause 2. Now that linting is a single job this is much easier to contain, but pinning an upper bound or switching to NewCops: disable is a policy call I have left to you.

Not addressed

The rails_edge spec failures are genuine upstream API drift — ActionDispatch::ExceptionWrapper.rescue_responses is now frozen, and ActionController::LogSubscriber.attach_to is gone. Likewise ci (jruby-10.0, rails_edge) fails because the herb gem's native extension won't build on JRuby. All of these are already continue-on-error and don't fail the run, so I've left them out rather than widen this PR. Happy to take them separately.

Note for #401

This touches the same gemspec comment block as #401, so that PR will need a trivial rebase. Its .rubocop.yml change becomes redundant once this lands — the exclusion here uses RuboCop's conventional **/*.gemspec glob.

🤖 Generated with Claude Code

johnnagro and others added 2 commits September 21, 2026 18:10
CI has been red for every PR since json 3.0.0 was released. The failures are
environmental rather than caused by any change in the repo: `master`'s last CI
run predates json 3.0.0, so the breakage went unnoticed.

Four independent causes, none related to lograge itself:

1. json 3.x + ActiveSupport <= 8.0 (67 blocking jobs)

   json 3.0 promoted unknown generator/parser options from "ignored" to a fatal
   ArgumentError. ActiveSupport <= 8.0 still passes `quirks_mode`, so any
   `to_json` raises `unknown keyword: quirks_mode`. Two test-only formatter
   lambdas used `data.to_json` and tripped over it; lograge's own formatters
   already use `JSON.dump` and were never affected. Switch the lambdas to
   `JSON.dump` so the suite is independent of the ActiveSupport encoder.

   Rails has fixed this on 8-0-stable, 8-1-stable and main, but no released
   version carries the fix yet.

2. RuboCop 1.91's new Style/DirectiveScope cop (6 blocking jobs)

   `NewCops: enable` plus an unbounded `rubocop` dependency means new cops
   activate as soon as they ship. The cop's autocorrect rewrites the
   disable/enable pairs to `# rubocop:disable-next`, a directive that RuboCop
   < 1.90 does not understand -- which would have broken the older-Ruby legs
   instead. Move both suppressions into .rubocop.yml, which every version in
   the matrix reads the same way.

   Also restate the gemspec exclusion for Metrics/BlockLength: a local Exclude
   replaces RuboCop's default list rather than extending it, so the gemspec
   lost its default exemption and sits right at the 25-line limit.

3. Rails edge now requires Ruby >= 3.3.1, so the 3.2 leg cannot resolve.
   Drop that combination from the matrix.

4. Ruby 4.1 extracted `benchmark` from the default gems, so the ruby-head legs
   fail to load ActiveSupport. Declare it explicitly, alongside the existing
   base64/bigdecimal/mutex_m entries.

Verified locally: `rake` passes against both json 3.0.2 and 2.21.2, and
RuboCop is clean on 1.50.2, 1.56.4, 1.60, 1.80, 1.90 and 1.91 -- covering the
range the matrix resolves from Ruby 2.6 through head.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first push fixed the spec failures, which then exposed a pre-existing
RuboCop crash on TruffleRuby. It had been visible only on the one leg where
specs already passed (truffleruby/rails_8.1); with specs green everywhere it
started failing all nine TruffleRuby legs:

    53 files inspected, no offenses detected
    5 errors occurred:
    An error occurred while Lint/MissingCopEnableDirective cop was inspecting ...
    Errors are usually caused by RuboCop bugs.

All five are in cops that parse inline `rubocop:` directives, and RuboCop
itself reports them as its own bug rather than an offense in this code.

Rather than work around a RuboCop/TruffleRuby bug, stop running the linter
across the matrix. Two problems were stacked here:

- `rake` already runs `spec` + `rubocop`, and the workflow then ran
  `bundle exec rubocop` again, so every leg linted twice.
- RuboCop resolves to a different version on each Ruby (1.50.2 on 2.6 through
  1.91 on 3.4), so lint results depended on which leg ran. That is precisely
  what broke the matrix when 1.91 shipped Style/DirectiveScope.

The matrix now runs `rake spec`, and a single `lint` job runs RuboCop once on
one Ruby. Linting is platform-independent, so this loses no coverage while
making results deterministic and dropping ~180 redundant RuboCop runs. The
default `rake` task is unchanged for local development.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@johnnagro
johnnagro merged commit d5b43c6 into master Sep 21, 2026
85 of 94 checks passed
@johnnagro johnnagro mentioned this pull request Sep 29, 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