CI: the gate that decides whether a workflow runs was queued behind the work it gates #603

Merged
dcharlot merged 2 commits from ci/gates-off-arm64-r2 into main 2026-11-03 17:41:39 -05:00
Owner

Every path-gated workflow in this repository opens with a cheap gate job — relevant, shards or plan — that reads the pull request's diff and answers whether the real work should run. Every real job in the workflow declares that gate in needs, so nothing in the workflow starts until the gate finishes.

58 of the repository's 66 gate jobs ran on linux-arm64, which is the fleet's most contended label.

That is head-of-line blocking, and it is the expensive kind: a script that takes ten seconds waits in the same queue as jobs that take an hour, and while it waits, every other runner the workflow would use sits idle, because no job may begin until the gate has spoken. A gate rations a queue it should never be standing in.

This is not hypothetical. It deadlocked a stack of pull requests earlier today: two of them had nothing left to settle but gate scripts, those gates were queued behind hour-long falsification shards on linux-arm64, and a macOS job was reported blocked waiting on one of them while the linux runners had no work at all.

This moves all 58 gate jobs to linux. The work jobs do not move. Where builds, tests and proofs run is a deliberate choice per workflow and is untouched here; only the question "should this workflow run at all" changes queue.

The second commit

The first pass found the gates by the sentence they share in their name: — does this change touch it. Four workflows prefix theirs (WAI HTTP · does this change touch it, WAI breaking · …, WAI examples · …, wai-pack · …), and one names itself for its job entirely, so five gates were missed by the sweep that claimed to be exhaustive.

Two of them are in wai-http.yml, which is the worst file to have missed: its gate decides whether six falsify shards run, and the file's own comment puts those at about an hour and a half of runner time. Both the gate and the work it gates were in the same queue.

The second commit finds them by the job key instead of the display name, because the key is the structural fact and the name is prose.

Verification

Checked with a different query from the one that found the targets, so the check cannot inherit the original mistake:

  • Every gate-class job in the repository, by label: {"linux" => 66} — none remain on linux-arm64 or anywhere else.
  • All 77 workflows parse.
  • check-workflow-concurrency.sh: 77 workflows, each grouped per ref.
  • check-tests-in-ci.py and check-lockfiles.sh pass.

Why this replaces #598

Same two commits, rebased onto main after #599 and #602 landed. #598's three red checks were all explained and are all now fixed on main: joulespec-proofs was the proof-gate bug that #599 repairs, kani (isqrt512) was the shard that #602 moves to a host where it fits, and the android leg was a timing flake on a build host that was at load 476 from orphaned simulator processes since reaped. Rebasing in place needs a force-push, which is not available here, hence a new branch.

#599 contained the identical plan change in standards-build.yml, so git applied it once; that hunk is already on main and is correctly absent from this diff, which is why this is 58 jobs across 57 files rather than 59 across 58.

🤖 Generated with Claude Code

Every path-gated workflow in this repository opens with a cheap gate job — `relevant`, `shards` or `plan` — that reads the pull request's diff and answers whether the real work should run. Every real job in the workflow declares that gate in `needs`, so nothing in the workflow starts until the gate finishes. 58 of the repository's 66 gate jobs ran on `linux-arm64`, which is the fleet's most contended label. That is head-of-line blocking, and it is the expensive kind: a script that takes ten seconds waits in the same queue as jobs that take an hour, and while it waits, every *other* runner the workflow would use sits idle, because no job may begin until the gate has spoken. A gate rations a queue it should never be standing in. This is not hypothetical. It deadlocked a stack of pull requests earlier today: two of them had nothing left to settle but gate scripts, those gates were queued behind hour-long falsification shards on `linux-arm64`, and a macOS job was reported `blocked` waiting on one of them while the `linux` runners had no work at all. This moves all 58 gate jobs to `linux`. **The work jobs do not move.** Where builds, tests and proofs run is a deliberate choice per workflow and is untouched here; only the question "should this workflow run at all" changes queue. ## The second commit The first pass found the gates by the sentence they share in their `name:` — `does this change touch it`. Four workflows prefix theirs (`WAI HTTP · does this change touch it`, `WAI breaking · …`, `WAI examples · …`, `wai-pack · …`), and one names itself for its job entirely, so five gates were missed by the sweep that claimed to be exhaustive. Two of them are in `wai-http.yml`, which is the worst file to have missed: its gate decides whether six `falsify` shards run, and the file's own comment puts those at about an hour and a half of runner time. Both the gate and the work it gates were in the same queue. The second commit finds them by the job *key* instead of the display name, because the key is the structural fact and the name is prose. ## Verification Checked with a different query from the one that found the targets, so the check cannot inherit the original mistake: - Every gate-class job in the repository, by label: `{"linux" => 66}` — none remain on `linux-arm64` or anywhere else. - All 77 workflows parse. - `check-workflow-concurrency.sh`: 77 workflows, each grouped per ref. - `check-tests-in-ci.py` and `check-lockfiles.sh` pass. ## Why this replaces #598 Same two commits, rebased onto `main` after #599 and #602 landed. #598's three red checks were all explained and are all now fixed on `main`: `joulespec-proofs` was the proof-gate bug that #599 repairs, `kani (isqrt512)` was the shard that #602 moves to a host where it fits, and the android leg was a timing flake on a build host that was at load 476 from orphaned simulator processes since reaped. Rebasing in place needs a force-push, which is not available here, hence a new branch. #599 contained the identical `plan` change in `standards-build.yml`, so git applied it once; that hunk is already on `main` and is correctly absent from this diff, which is why this is 58 jobs across 57 files rather than 59 across 58. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Every path-gated workflow begins with one small job -- `does this change touch
it`, or `which workspaces this change touches` for the standards build -- that
all of its real jobs declare in `needs`. All 55 of those gate jobs ran on
`linux-arm64`.

That is the one label the fleet cannot spare. Sampled while 267 runs were
queued, every waiting run had the same shape:

    [waiting]  linux-arm64    does this change touch it
    [blocked]  linux          member · dataflow device, simulated
    [blocked]  macos          member · dataflow device absent

237 runs, each holding a gate in the longest queue, and every real job blocked
behind it. Not only `linux` jobs: `macos`, `linux-hw`, `linux-arm64-hw` and
`macos-m3-ultra` jobs were blocked too. The measured effect was `linux-arm64`
263 deep against `linux` **zero**, with jetson-hub's 20 cores taking no job for
a full hour -- starved not for want of work but because the work could not be
unblocked. A gate waiting on the resource it is rationing is head-of-line
blocking, and it makes every other runner in the fleet idle on its behalf.

The gates move to `linux`. Nothing else changes: the diff is 55 `runs-on` lines
and no others, and every touched file still parses.

Why `linux` and not elsewhere. The gate is not free -- it compares
`BASE...HEAD`, which needs the merge base, so `fetch-depth: 0` is a full clone
rather than a ten-second script. `macos` is served by hosts on a Mac Studio
sitting at load 185; `linux` is a separate 20-core box that was measurably
empty. This moves a serialisation point to the least contended label; it does
not remove it, and the gate is still a single point of failure for its
workflow. The structural fix is an early-exit first step inside each job, which
costs one checkout per leg and is a larger change.

Follow-up, deliberately not bundled: jetson-hub runs at capacity 2 with 15 GB of
RAM, so 55 clones still serialise two at a time. Raising capacity helps gates
and hurts the heavy emulated-architecture jobs that share the label, so it wants
measuring on its own.

Co-Authored-By: Claude Opus 5 <[email protected]>
CI: five more gates were in the queue they ration, missed by their own names
All checks were successful
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 14s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 30m3s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 41s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 6m11s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 7m32s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 2m24s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 21m41s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 8m47s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 9m59s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 40m28s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 2m56s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 9m18s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 2m1s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m50s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 30s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 16s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 48m29s
wai-ops integer operators / linux-aarch64 (pull_request) Successful in 1m41s
wai-ops-npu integer operators on neural processors / builds · no device is not a pass (linux-arm64) (pull_request) Successful in 1m0s
wai-quantum HIP member / member · HIP absent (linux-arm64) (pull_request) Successful in 1m24s
wai-quantum npu-cores member / member · npu-cores absent (linux-arm64) (pull_request) Successful in 2m14s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (pull_request) Successful in 2m10s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 49m4s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m38s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (pull_request) Successful in 2m27s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 3m7s
wai-quantum extension points / the registry and the example · linux-arm64 (pull_request) Successful in 6m9s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 3m28s
wai-vector vector graphics / linux-aarch64 (pull_request) Successful in 4m14s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h41m35s
944a813684
The commit before this one moved 55 gate jobs off `linux-arm64`. It found them
by the name every gate carried, `does this change touch it`. Four workflows
prefix theirs -- `WAI HTTP · does this change touch it`, `WAI breaking · does
this change touch WAI`, `WAI examples · does this change touch them`,
`wai-pack · does this change touch it` -- so a name match skipped them, and the
fix that was meant to be exhaustive left five jobs behind.

Searching on the job key instead of the display name finds them. They are the
same shape as the 55: `fetch-depth: 0`, one call to pr-touches-paths.py, and
every real job in the file declaring them in `needs`. The fifth is wai-http's
`shards`, the accounting job that checks each patch and mutation lands in
exactly one shard; it runs one bash script and gates nothing cheaper than
itself, so it belongs on the same label as wai-formal-verification's.

wai-http is the costly one to leave behind. Its six falsify shards are about an
hour and a half of runner time, its gate decides whether they run at all, and
the gate's own regex lists `.github/workflows/wai-http.yml`, so any branch that
edits that file -- every Rust-toolchain branch edits all of them -- waits on an
arm64 gate to find out it must then spend that hour and a half on arm64 too.

Measured while this was written: `linux-arm64` 188 runs deep, `linux` idle, and
the only jobs two pull requests still needed to merge were gate scripts of a
few seconds each, queued behind hour-long work. The work stays where it is.
Only the gates move.

Verified: searching by job key now finds no gate-class job on `linux-arm64`,
and all four files parse.

Co-Authored-By: Claude Opus 5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!603
No description provided.