Fix intermittent server boot failure caused by working directory race - #727
Merged
Conversation
soutaro
force-pushed
the
fix-runner-client-boot-race
branch
from
August 5, 2026 04:25
afd05bb to
8510c69
Compare
RunnerClient.create_client checked for bin/rails relative to the current working directory and spawned the server child process with the inherited working directory. The working directory is process global state and other threads may change it concurrently: RuboCop's config loader temporarily switches to a gem's directory while evaluating configurations inherited with inherit_gem. When the Ruby LSP RuboCop add-on activation overlaps the Rails add-on's boot thread, the boot thread can observe the gem's directory as the working directory, fail the bin/rails check, and silently fall back to a NullClient. Tests busy-wait for the client to stop being a NullClient, so the whole suite hung. Ruby 4.0.6 changed thread context switching (backport of Bug #21685), which made this long-latent race fire frequently on CI runners. Resolve bin/rails against the workspace path from the global state instead, and spawn the server with an explicit chdir to the workspace path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soutaro
force-pushed
the
fix-runner-client-boot-race
branch
2 times, most recently
from
August 5, 2026 04:41
7f4ef80 to
4f159e4
Compare
Several tests busy-waited with `sleep(0.1) while ...NullClient` until the Rails add-on's boot thread replaced the NullClient with a real client. When booting fails, the NullClient is the final state, so these loops never terminate and the whole suite hangs until the CI job times out. Add Addon#join_boot_thread so the boot outcome can be awaited deterministically, and make the tests join the boot thread and assert that the resulting client is not a NullClient, so a boot failure fails the test immediately with a clear message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soutaro
force-pushed
the
fix-runner-client-boot-race
branch
from
August 5, 2026 04:44
4f159e4 to
b753e66
Compare
soutaro
marked this pull request as ready for review
August 5, 2026 04:59
st0012
approved these changes
Aug 5, 2026
Morriar
approved these changes
Aug 5, 2026
jesse-shopify
approved these changes
Aug 5, 2026
jesse-shopify
left a comment
Contributor
There was a problem hiding this comment.
I really like seeing the removal of all the sleep calls. This is a much improved design.
soutaro
force-pushed
the
fix-runner-client-boot-race
branch
from
August 7, 2026 01:01
c47b378 to
6b7f6fa
Compare
Covers both halves of the working directory fix: locating bin/rails and spawning the server both have to use the workspace path rather than the process working directory. The test fails against either half being reverted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soutaro
force-pushed
the
fix-runner-client-boot-race
branch
from
August 7, 2026 01:07
6b7f6fa to
35edca5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
CI hangs intermittently on Ruby 4.0.6 (first seen in #724 / #725) — jobs time out after 25 minutes with the test suite stuck mid-run. The cause is a working-directory race in
RunnerClient.create_client, not setup-ruby or gem updates.create_clientchecksFile.exist?("bin/rails")relative to the process working directory, and spawns therails runnerserver with the inherited cwd. But the working directory is process-global state that other threads mutate: RuboCop's config loader evaluates configs withDir.chdir(config dir) { ERB.new(...).result }while resolvinginherit_gem. When the Ruby LSP RuboCop addon activates concurrently with the Rails addon boot thread, the boot thread can observe therubocop-shopifygem directory as cwd. Thebin/railscheck then fails andcreate_clientsilently falls back to aNullClient.The race is long-standing but almost never fired until Ruby 4.0.6, whose thread-scheduler changes (Bug #21685 backport) altered thread interleaving enough to make it fire several times per 100 boots on CI runners. That's why bumping setup-ruby past 1.317.0 (which resolves
"4.0"to 4.0.6 instead of 4.0.5) broke CI. Trace from a failing boot captured in the investigation PR (#726, now closed):Note that the hang itself only happens in this repo's test suite: several tests busy-wait with
sleep(0.1) while ...NullClientfor the real client to boot, and the loop never terminates when boot fails becauseNullClientis the final state. In real editor usage a boot failure does not hang anything — the add-on falls back to theNullClient, logs a warning, and Rails-dependent features are silently unavailable.Implementation
bin/railsagainstglobal_state.workspace_pathinstead of the cwd, and spawn the server with an explicitchdir:to the workspace path, so neither depends on racy process-global state.Addon#join_boot_threadso the boot outcome can be awaited deterministically, and make the tests join the boot thread and assert that the client actually booted. A boot failure now fails the test immediately with a clear message instead of hanging the suite until the CI timeout.