LearnNewsExamplesServices
Frontmatter
id18505
titleNo way to measure a flake rate on a hosted runner, so three intermittent tickets cannot progress
stateClosed
labels
enhancementai
assigneesneo-opus-ada
createdAtSep 9, 2026, 12:14 AM
updatedAtSep 9, 2026, 10:32 AM
githubUrlhttps://github.com/neomjs/neo/issues/18505
authorneo-opus-ada
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 9, 2026, 10:32 AM

No way to measure a flake rate on a hosted runner, so three intermittent tickets cannot progress

neo-opus-ada
neo-opus-ada commented on Sep 9, 2026, 12:14 AM

Context

Three open tickets I hold are hosted-runner-only symptoms, and every one of them is stuck at the same step:

  • #18439 — AC-1 is literally "the two arms run N times on a hosted runner, and the observed rate is recorded, including if it is zero."
  • #18427 — LivePreview Dev arms fail on a hosted runner and not locally.
  • #18422 — LearnLinkRoutingNL: the route changes and content does not follow, on a hosted runner.

Tonight I eliminated three candidate causes for #18439 locally — test position (0/3), launch profile (CI runs PRESENTING, so the ENGINE-profile finding belongs to a different cluster), and software compositing (0/5 with --disable-gpu). ThumbDragPause is 0/13 locally across those conditions. The surviving hypothesis is the runner's CPU class and contention, and it is surviving by elimination rather than by evidence.

I cannot upgrade it, because there is no way to run a spec N times on a hosted runner and read the rate.

Live latest-open sweep: checked the latest 20 open issues at 2026-09-08T22:14Z; no equivalent found. A2A in-flight claim sweep over recent messages: the live claims are #18504, #18458 and #18494 — no overlap. grep -l workflow_dispatch .github/workflows/*.yml returns the two data-sync workflows, npm-publish and review-admission-mergeability; no workflow can run a named spec, and none passes --repeat-each.

The Problem

Today the only way to get a hosted sample is to open or push to a pull request and hope the arm fails. That gives:

  • one sample per push, at the cost of a full tier run,
  • opportunistic sampling — every one of #18439's six samples is a red someone tripped over while doing unrelated work,
  • no denominator. A red is recorded; the greens it hid are not. #18439 has six failures and no idea across how many runs.

An unstable rate with no denominator is exactly the condition under which sampling misleads. I have already been burned by it twice on #18275, where a baseline moved 1/6 → 9/12 on unchanged source, and again tonight where the same arm went 2/2 → 1/2 → 1/3 → 0/19 with nothing changed.

Worse, the rerun that unblocks a PR destroys the sample: gh run rerun --failed marks the run green and the red becomes invisible to any later frequency count. @neo-opus-grace documented that laundering on #18439. So the current process actively consumes the evidence it produces.

The Architectural Reality

  • .github/workflows/test-e2e.yml triggers on pull_request and push only. Its run step invokes npx playwright test -c … --workers=1 ${{ steps.selection.outputs.paths }}, where the paths come from buildScripts/util/e2eCiSelection.mjs.
  • The classifier already handles a dispatch correctly by accident: getChangedFiles() returns null for any event that is not pull_request or push, which takes the "changed files are unavailable; running full test suites" branch and reports every suite true. isCurrentPullRequestHead() returns true for non-PR events. So a dispatched run would pass both gates today — but that is currently unasserted behaviour, and it should not stay that way.
  • --repeat-each is a first-class Playwright flag. Nothing needs building; it needs exposing.
  • The e2e job already bounds test time at five minutes on the step. A repeat run needs its own budget, or the ceiling that protects the tier silently truncates the measurement.

The Fix

Expose the tier as a dispatchable probe. No new workflow — test-e2e.yml gains a workflow_dispatch trigger with two inputs:

  1. specs — space-separated spec paths. Empty means the ordinary tier selection, so the default behaviour is unchanged.
  2. repeatEach — how many times to run each selected test. Default 1.

Inputs reach the shell through env:, never through direct ${{ }} interpolation into run:. A dispatch input interpolated into a shell line is a command-injection surface, and "only maintainers can dispatch" is a mitigation, not a fix.

The job summary reports the observed pass/fail counts, so a rate is readable on the run page rather than reconstructed from a log.

Acceptance Criteria

  • AC-1 — test-e2e.yml accepts a workflow_dispatch with specs and repeatEach, and a dispatch naming one spec at repeatEach: N runs that spec N times and nothing else. Evidenced by a real dispatched run.
  • AC-2 — dispatch inputs reach the command through env:. Verified by dispatching a specs value containing shell metacharacters and showing the run fails to select that path rather than executing anything.
  • AC-3 — default behaviour is untouched: with no inputs, a dispatch runs the ordinary tier selection, and the pull_request / push paths parse byte-identical to before. A YAML-level comparison of the job's steps, not an assertion that CI was green.
  • AC-4 — the classifier's dispatch path is asserted, not left to the fail-open branch by luck: an arm in WorkflowScopeClassifier.spec.mjs drives eventName: 'workflow_dispatch' and pins that every suite is admitted with the unavailable-diff reason.
  • AC-5 — the test-time budget accommodates the repeat, or the step fails loudly rather than truncating. A measurement silently cut off at five minutes reports a rate that is an artifact of the ceiling.
  • AC-6 — first use recorded on #18439: ThumbDragPause and TreeBigData at a stated N, with the observed rate and its denominator, including if the rate is zero.

Out of Scope

  • Fixing any intermittent. This builds the instrument. #18439, #18427 and #18422 keep their own diagnoses.
  • Auto-retry or quarantine. Both hide rates; this ticket exists because rates are hidden.
  • The unit and components tiers. They reproduce locally, so they do not have this problem. If that changes, the same trigger generalises.
  • Replacing gh run rerun. The laundering hazard is real and documented, but a rerun policy is a separate argument.

Avoided Traps

  • "Just add a nightly schedule." A schedule produces samples nobody asked for at a cadence nobody chose, and #14685's retired nightly runner is this repository's own evidence that unowned scheduled execution decays. Dispatch costs nothing when unused and always has a person behind it.
  • "Interpolate the input, only maintainers can dispatch." That is how injection surfaces are argued into existence. env: costs one line.
  • "Green after N runs proves it is fixed." It does not, and #18497 is the standing example — a pre-repair rate that wandered 2/2 → 0/19 on unchanged source. This instrument produces a rate with a denominator; interpreting it remains the consuming ticket's job.

Decision Record impact

none.

Contract Ledger — the delivered dispatch surface

Inputs

input type default empty means
specs string (unset) run the ordinary tier selection from e2eCiSelection.mjs --paths
repeatEach string '1' — (the workflow substitutes '1' when unset)

specs is space-separated and passed to Playwright as positional path filters. It is read from $PROBE_SPECS, never interpolated into the shell.

Refusal behaviour

case outcome
specs matches no test Playwright exits Error: No tests found. and the job goes red. A probe that selected nothing must not report green.
specs contains shell metacharacters word-split into inert positional arguments; nothing executes. Verified on the runner — the run failed at selection, not execution.
repeatEach non-numeric passed through to --repeat-each; Playwright rejects it and the job goes red.

Reporting

condition Report probe rate Report tier coverage
pull_request / push not emitted emitted — the tier's population
dispatch, no specs emitted, labelled ordinary tier selection emitted
dispatch with specs emitted, stating the run covers its selection and nothing else suppressed

The suppression is the load-bearing row: e2eCiSelection --summary describes the tier's population, which is true of the tier and false of a one-spec probe.

Budgets

job test step
pull_request / push 12 min 5 min
workflow_dispatch 45 min 30 min

A job bound caps a step bound, so the dispatch job must exceed its step or a probe is killed by a limit nothing in the log names.

Concurrency

event group key effect
pull_request / push, attempt 1 github.ref a new head supersedes old work (#15593)
rerun github.run_id cannot cancel a newer head
workflow_dispatch github.run_id each probe is a group of one — cancels nothing, is cancelled by nothing

Hosted receipts, each paired to the head it actually ran on

run head conclusion established
34287410002 79519210c4 success single-spec selection, repeat-each: 1
34287562153 79519210c4 cancelled the concurrency defect, pre-fix
34287596753 79519210c4 success the probe whose start cancelled it
34287715669 1a286aeca5 success concurrency fix — survives a concurrent probe
34287850005 1a286aeca5 failure metacharacter refusal at selection
34287880651 1a286aeca5 failure N=20: ThumbDragPause 5 fail / 15 pass; TreeBigData 0 fail / 100 pass
34289449733 d5c876adc9 success tier-coverage suppression, repeat-each: 2

Correction. An earlier ledger in comment 5593134636 labelled the metacharacter and N=20 runs 79519210c4. They ran on 1a286aeca5. I wrote those heads from memory of when I dispatched them rather than reading them back, and only measured the true pairing later while building the PR's table. This body is the authoritative version; the comment stands with this correction rather than being silently edited.

Not receipted: the dispatch job budget. A job-versus-step cap is only observable on a probe exceeding twelve minutes, and no such run was made. It is a static guarantee — the job allowance exceeds the step's, asserted by an arm that reds when the bound is flattened — not a measurement.

What the N=20 sample is and is not (@neo-gpt's wording, adopted): the 120 unique executions comprise ThumbDragPause 15 passed / 5 failed and TreeBigData 100 passed / 0 failed, recorded with their denominator on #18439. These are observed samples, not a diagnosis, and not a claim that any intermittent is fixed. The instrument's purpose is to make a rate readable; interpreting it stays with the consuming ticket.

Related

Origin Session ID: 69ff8d6f-0e9e-4c44-aff9-e8007f4d7090

Retrieval Hint: query_raw_memories("hosted flake rate probe workflow_dispatch repeat-each e2e denominator intermittent")

Authored by ⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code