Skip to content

Add Ruby 4.0.6 as the default version, with its own CI suite - #204

Merged
simonx1 merged 2 commits into
mainfrom
ci/ruby-4.0.6
Aug 3, 2026
Merged

Add Ruby 4.0.6 as the default version, with its own CI suite#204
simonx1 merged 2 commits into
mainfrom
ci/ruby-4.0.6

Conversation

@simonx1

@simonx1 simonx1 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Adds Ruby 4.0.6, gives it a dedicated CI suite, and makes it the project's default Ruby.

  • .ruby-version (new, tracked) pins the default to 4.0.6. The repo already had .tool-versions, but it is gitignored (.gitignore:14), so it could only ever set a personal default — it would not have made 4.0.6 the default for anyone else. .ruby-version is read by rbenv, chruby, mise and ruby/setup-ruby.
  • New test-ruby-4 CI job runs RuboCop + RSpec on 4.0.6 as its own suite, listed first.
  • The existing matrix job is untouched at ['3.2', '3.3'] — that is the compatibility floor the gemspec still declares (required_ruby_version >= 3.2.0). Keeping it as-is also means the existing test (3.2) / test (3.3) check names don't change, so any branch protection rules keep working.

Two things Ruby 4 actually broke

Both were found by running the suite on 4.0.6, not by inspection:

  1. Hash#inspect format. spec/lib/mcp_client/logging_spec.rb asserted {"key"=>"value"}; Ruby 3.4 changed the format to {"key" => "value"}, so the spec failed on 4.0.6. The library itself is fine — it just forwards whatever #inspect returns — so the spec now compares against payload.inspect of the same hash, which is correct on every version rather than pinned to one.

  2. ffi 1.17.2 has no Ruby 4 ABI build for arm64-darwin. With the old lock, bundle install on Apple Silicon re-resolved to 1.17.4 on every run, dirtying Gemfile.lock. Bumped to 1.17.4, which ships an arm64-darwin build covering Ruby 4. (1.17.2 does still compile from source on 4.0.6 — I checked — so Linux CI was never blocked; this is about lockfile stability for local development, which matters now that 4.0.6 is the default.) Verified the suite still passes on 3.3.5 with 1.17.4.

What I deliberately did not change

  • gemspec required_ruby_version stays >= 3.2.0. Supporting 4.0 doesn't raise the floor, and 3.2/3.3 remain tested.
  • .rubocop.yml TargetRubyVersion stays 3.3.5. It should track the oldest supported Ruby, not the newest — bumping it to 4.0 would let RuboCop suggest Ruby-4-only idioms that break the 3.2 users the gemspec promises to support.

Testing

Ran locally against a real 4.0.6 install (ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM), not just in CI:

Ruby RSpec RuboCop
4.0.6 (new default) 1606 examples, 0 failures 131 files, no offenses
3.3.5 1606 examples, 0 failures 131 files, no offenses

3.2 is covered by CI only — I don't have that interpreter locally.

Note on overlap with #203

#203 pins the CI actions to commit SHAs and touches the same file. Whichever lands second needs a trivial merge; if this one lands first, the new job's actions/checkout@v4 / ruby/setup-ruby@v1 should be pinned the same way as the rest. I kept this job's style matching its neighbours on main so the file stays internally consistent either way.

🤖 Generated with Claude Code

- .ruby-version (tracked) pins the project default to 4.0.6. The existing
  .tool-versions is gitignored, so it could only ever set a personal
  default; .ruby-version is read by rbenv/chruby/mise and by
  ruby/setup-ruby.
- CI gains a dedicated test-ruby-4 job running RuboCop and RSpec on
  4.0.6. The existing matrix job keeps 3.2/3.3 as the compatibility
  floor the gemspec still declares (>= 3.2.0), so its check names are
  unchanged.
- One spec asserted the pre-3.4 Hash#inspect format ({"key"=>"value"});
  Ruby 3.4 added spaces around the arrow. It now compares against
  #inspect of the same hash, which is correct on every version — the
  client only forwards whatever #inspect produced.
- Gemfile.lock: ffi 1.17.2 -> 1.17.4. The 1.17.2 prebuilt arm64-darwin
  gem has no Ruby 4 ABI build, so `bundle install` on Apple Silicon
  drifted the lock on every run. 1.17.4 ships one; verified on 3.3.5 too.

Verified locally: full suite and RuboCop pass on both 4.0.6 and 3.3.5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@simonx1

simonx1 commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

Codex independent verification

I asked Codex (codex-cli 0.146.0, adversarial review, branch diff vs main) to independently check this change.

Verdict: approve — no material findings.

Ship. Main's Ruby 4 incompatibilities were real: the old spec expected the pre-3.4 Hash#inspect output, and ffi 1.17.2's arm64-darwin gem excluded Ruby 4. The diff fixes both; ffi 1.17.4 supports Ruby 3.0–4.0 and includes the Ruby 4 binary. .ruby-version, CI, and documentation consistently select 4.0.6, while 3.2/3.3 remain tested. No production error, retry, or runtime dependency semantics changed. Ruby 4.0.6 logging specs and RuboCop passed; no material regression was found.

The useful independent check here is the ffi range: I verified 1.17.4 works on 3.3.5 by running the suite, but Codex confirmed from the gem metadata that it declares support all the way down to 3.0 — so the bump can't strand the 3.2 floor that CI tests but I can't run locally.

It also confirmed the .ruby-version / CI / README trio are mutually consistent, which was the thing most likely to drift given .tool-versions is gitignored and can't carry the default.

Verification

Unchanged since the original push: CI green on all three suites — Test (Ruby 4.0.6, default), test (3.2), test (3.3) — with the 4.0.6 job's log confirming ruby 4.0.6 ... +PRISM [x86_64-linux], 1606 examples / 0 failures, RuboCop clean.

Codex review requested and summarized by Claude Code; the quoted text is Codex's, the framing is mine.

# Conflicts:
#	.github/workflows/ci.yml
@simonx1
simonx1 merged commit ff5c2ad into main Aug 3, 2026
3 of 4 checks passed
simonx1 added a commit that referenced this pull request Aug 3, 2026
## Summary

Follow-up to #204, found while verifying merged `main`.

Making Ruby 4.0.6 the default left `Gemfile.lock` pinning `BUNDLED WITH
2.5.16`, which predates Ruby 4. Locally that combination breaks:

- every child process spawned from `bundle exec` inherits
`RUBYOPT=-rbundler/setup`, and bundler 2.5.16 floods stderr with
`already initialized constant Gem::Platform::*` against Ruby 4's
RubyGems while re-resolving;
- that delayed the two **real-subprocess** stdio compliance specs past
their timeouts (`server_stdio_compliance_spec.rb:38` and `:54`), and
made the whole suite ~4× slower (3m59s vs 56s).

I confirmed it is not related to the security merges: the same two specs
fail with the **pre-merge** stdio code under 4.0.6, and pass on 4.0.6 as
soon as a compatible bundler is used.

CI never caught this because `ruby/setup-ruby` installs its own bundler
rather than the one in the lockfile — so it only affected anyone
following `.ruby-version`, which is exactly what #204 told people to do.

## Fix

`BUNDLED WITH` → `4.0.17`. It declares `required_ruby_version >= 3.2.0`,
the same floor as this gem, so it covers all three tested Rubies (4.0.6,
3.3, 3.2).

## Testing

| | before | after |
|---|---|---|
| `server_stdio_compliance_spec.rb` on 4.0.6 | 2 failures | 10 examples,
0 failures |
| full suite on 4.0.6 | 2 failures, 3m59s | **1693 examples, 0 failures,
56s** |

RuboCop clean (136 files).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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