Skip to content

ci(smoke): say which side is wedged when readiness times out - #1415

Merged
FerroxLabs merged 1 commit into
mainfrom
ci/diagnose-winarm64-readiness
Sep 19, 2026
Merged

FerroxLabs merged 1 commit into
mainfrom
ci/diagnose-winarm64-readiness

Conversation

@FerroxLabs

Copy link
Copy Markdown
Owner

win32-arm64 has failed readiness ten times across v0.13.2 while the app plainly booted. The failure message is always the same, and it is not enough to act on:

Electron renderer did not become ready: CDP command timed out: Runtime.evaluate

It cannot distinguish a wedged RENDERER from a wedged MAIN process. That single missing bit is what four separate fixes have been guessed against.

Why guessing has to stop

Two of those fixes were mechanically correct and still did not change the outcome:

Same failure. Both were aimed at the main process, and nothing has ever established that the main process is the blocked side.

The failure is also bimodal, which rules out the remaining timeout theories: the one passing leg reached readiness in 113s, and every failing leg never reached it in 600s. That is a hang, not slowness. No further budget widening can fix a hang.

What this adds

On a readiness timeout the harness now asks the browser endpoint, which the main process serves rather than the renderer:

Browser.getVersion answers  -> main process live, renderer wedged
Browser.getVersion hangs    -> the main process is the blocked one

and prints the target list next to it, which says whether the page still exists at all.

Every probe is bounded and swallowed. Diagnostics describe a failure, they never change it, so a probe that itself fails is reported rather than thrown.

Verification

  • platformPackageSmoke: 71 pass, 0 fail
  • scoped oxfmt --check clean, node --check clean
  • mutation-proven: stubbing the diagnostics string fails the test with "expected ... to contain 'browser endpoint did NOT answer'"

One note on the test, because the first draft was wrong in a way worth naming. It asserted against runSmoke, which substitutes its own waitForRendererReady through the dependencies object, so the real function never ran and the test proved nothing. It now drives the exported waitForRendererReady directly against a dead port.

What this does not do

It ships nothing and fixes nothing. It converts the next win-arm64 failure from a dice roll into a diagnosis.

🤖 Generated with Claude Code

win32-arm64 has failed readiness ten times across v0.13.2 while the app
plainly booted - window created, renderer emitting telemetry - and then
Runtime.evaluate simply never returned. The failure said only:

  Electron renderer did not become ready: CDP command timed out: Runtime.evaluate

That message cannot distinguish a wedged RENDERER from a wedged MAIN
process, and that single bit is what four separate fixes were guessed
against. Two of them (#1411, #1414) were mechanically correct - detection
fell from ~32s to 12019ms exactly as predicted - and neither changed the
outcome, because neither was aimed at the real side.

So the timeout now asks the BROWSER endpoint, which the main process
serves rather than the renderer:

  Browser.getVersion answers  -> main process live, renderer wedged
  Browser.getVersion hangs    -> main process is the blocked one

and prints the target list alongside it, which says whether the page
still exists at all.

Every probe is bounded and swallowed. Diagnostics must describe the
failure, never change it, so a probe that fails is itself reported rather
than thrown.

The failure is bimodal, which is why this matters: the one passing leg
reached readiness in 113s, every failing leg never reached it in 600s.
That is a hang, not slowness, so no further timeout widening can fix it.

Gates: platformPackageSmoke 71 pass 0 fail, scoped oxfmt clean, node
--check clean. The new test drives the real exported waitForRendererReady
rather than the injected harness double - the first draft asserted against
runSmoke, which substitutes its own readiness function and so proved
nothing. Mutation-proven: stubbing the diagnostics string fails it with
"expected ... to contain 'browser endpoint did NOT answer'".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@FerroxLabs
FerroxLabs merged commit 73f811b into main Sep 19, 2026
24 checks passed
@FerroxLabs
FerroxLabs deleted the ci/diagnose-winarm64-readiness branch September 19, 2026 23:09
FerroxLabs added a commit that referenced this pull request Sep 20, 2026
#1415 established WHICH side hangs on win32-arm64: Browser.getVersion on
the browser endpoint went unanswered while the DevTools HTTP endpoint
still served /json/list, so the main process's JS thread is blocked, not
the renderer. It did not establish WHAT blocks it.

Agent detection is already excluded - it completes in 12278ms and the
block outlives it, and the app's last line is kickoff.telemetry about
12s after window creation, then silence for the remaining 600s.

A blocked JS thread cannot fire its own timer, so the timer is the probe.
A tick arriving late by more than its own period measures a stall the
event loop actually suffered; the last tick before silence dates a block
that never ends, which is enough to name whatever logged just before it.

Gated on WAYLAND_PACKAGE_SMOKE_MARKER, which only the packaged smoke
sets, so no shipped run pays for it. Extracted to its own module rather
than buried in index.ts so it can be tested at all - the same reason
describeDirtyShutdown was extracted.

Gates: full unit suite 1769 passed 0 failed, typecheck 0, scoped oxfmt
clean, oxlint 0 warnings. Mutation-proven on BOTH halves: removing the
smoke gate fails with "expected [Function] to be null", and disabling
stall detection fails because the stall logs as an ordinary tick. A
fourth test pins that ordinary timer jitter is NOT reported as a stall,
so the warning means something when it appears.

Co-authored-by: Claude Opus 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