Skip to content

[CI investigation] Ruby 4.0.6 hang: A/B test 4.0.5 vs 4.0.6 — do not merge - #726

Closed
soutaro wants to merge 9 commits into
mainfrom
ci-investigate-ruby-4.0.6-hang
Closed

[CI investigation] Ruby 4.0.6 hang: A/B test 4.0.5 vs 4.0.6 — do not merge#726
soutaro wants to merge 9 commits into
mainfrom
ci-investigate-ruby-4.0.6-hang

Conversation

@soutaro

@soutaro soutaro commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Do not merge — CI experiment only

The Ruby 4.0 on {ubuntu,macos}-latest with Gemfile jobs hang until the 25-minute job timeout on #724 and #725 (the ruby/setup-ruby bumps).

Evidence so far

  • setup-ruby 1.316.0 resolves ruby-version: "4.0" to Ruby 4.0.5; 1.319.0/1.321.0 resolve it to Ruby 4.0.6 (released 2026-07-14).
  • Bump actions/checkout from 7.0.0 to 7.0.1 #723 ran the same day as Bump ruby/setup-ruby from 1.316.0 to 1.319.0 #724 with the same freshly-resolved gems (lockfile is removed in CI) but still on setup-ruby 1.316.0 / Ruby 4.0.5 — all green.
  • In the failing runs, the printed lockfiles for the hanging Ruby 4.0 job and the passing Ruby 3.4 job are identical, so the only variable is the Ruby binary.
  • Only Gemfile (Rails ~> 8.1.0) hangs; Gemfile-rails-8-0 and Gemfile-rails-main pass on 4.0.6. Windows also passes.
  • Locally on an arm64 Mac (many cores) the suite passes on 4.0.6 + Rails 8.1.3.1, so the hang seems timing/environment dependent.

What this PR does

  • Narrows the matrix to Gemfile × Ruby {4.0.5, 4.0.6} × {ubuntu, macos} for a direct A/B comparison.
  • Runs Minitest with --verbose and synced stdout so the log shows which test is executing.
  • Sends SIGINT after 15 minutes so Minitest reports the interrupted test with a backtrace of where it was stuck.

Expected outcome

  • 4.0.5 jobs pass, 4.0.6 jobs hang → confirms the Ruby 4.0.6 regression and identifies the hanging test for an upstream report.

🤖 Generated with Claude Code

soutaro and others added 9 commits August 4, 2026 12:39
The 'Ruby 4.0 with Gemfile' jobs on ubuntu and macos started hanging
until the job timeout when ruby/setup-ruby was bumped past 1.316.0
(PR #724, #725). Newer setup-ruby resolves ruby-version '4.0' to
4.0.6 instead of 4.0.5, and PR #723 (same day as #724, still on
1.316.0/Ruby 4.0.5) passed, so Ruby 4.0.6 is the suspected culprit.

This narrows the matrix to Gemfile x {4.0.5, 4.0.6} x {ubuntu, macos},
runs Minitest verbosely with synced stdout, and interrupts the suite
with SIGINT after 15 minutes so the hanging test and its backtrace
appear in the log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
macos-latest runners have neither timeout nor gtimeout, so the previous
step failed immediately under bash -e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…achinery

The hung job's orphan-process list showed the spawned bundle/sh/ruby
chain still alive at cancellation, and the interrupted test was stuck
polling for the RunnerClient to finish booting. This job boots the
exact same subprocess in a loop to get a minimal repro and an in-process
backtrace via SIGINT/SIGABRT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on hang

server.rb start requires a capabilities JSON as ARGV[1]; without it every
iteration died immediately in JSON.parse. Also attach gdb/lldb before the
SIGINT escalation so a hang yields all-thread native backtraces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boot-loop result: 100 standalone boots of the rails runner server pass on
Ruby 4.0.6 on both ubuntu and macos, so the deadlock needs the threaded
test-process context. Drop the boot-loop job, focus the matrix on 4.0.6,
and on hang attach a debugger to parent and children to see which side is
stuck and where.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boots the runner server through with_server/addon activation exactly like
the feature tests, 120 times, with an in-process watchdog that dumps
Ruby-level backtraces of all threads after 120s of no progress. The shell
watchdog adds gdb/lldb native backtraces of parent and children.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
If the rails runner server fails to boot (or hangs during boot), kill it,
surface the failure reason (including the server's stderr) and retry up to
MAX_BOOT_ATTEMPTS times before falling back to NullClient.

Also warn() the failure reason in create_client so CI logs show why boots
fail, and apt-get update before installing gdb in the CI watchdogs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous run showed the boot thread vanishing without any warn and
without Ruby's report_on_exception output — meaning it likely finished
normally, i.e. took a NullClient path without raising. Trace every path
through create_client (entry with cwd, success, bin/rails-missing, giving
up, non-StandardError) and have the stress watchdog inspect the addon's
boot thread (status + stored exception via #value).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI tracing showed the intermittent hang happens when the boot thread checks
File.exist?("bin/rails") while another thread has temporarily changed the
process working directory — RuboCop's config loader does
Dir.chdir(config dir) { ERB.new(...).result } while resolving inherit_gem,
so the check sees the rubocop-shopify gem directory and create_client
silently returns a NullClient, which the tests busy-wait on forever.
Ruby 4.0.6's scheduler changes (Bug #21685 backport) made this pre-existing
race fire frequently on CI.

Use global_state.workspace_path for the bin/rails check and pass
chdir: workspace_path when spawning the server so neither depends on the
racy process-global working directory.

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