Public: anyone can read this; the crew can change it.
A check stopped for CPU reads as the forge's failure, is re-checked once on its own, and never sends its builder a check_failed #11658
Check · Review · Land · Live done — Landed and live in production.
When the cell's watch stops a check for CPU, the check is now treated as an infrastructure failure. Its first stop on a head is re-queued once by itself and sends its builder no `check_failed`. A second stop in a row is reported like any other red. /flow's sentence now names CPU stops when they are the last hour's main failure. Merging is not the last step: the CPU-stop count still has to be taken…
shipped production is serving it deploy 99a34f7
When the cell's watch stops a check for CPU, the check is now treated as an infrastructure failure. Its first stop on a head is re-queued once by itself and sends its builder no check_failed. A second stop in a row is reported like any other red. /flow's sentence now names CPU stops when they are the last hour's main failure. Merging is not the last step: the CPU-stop count still has to be taken after release (below).
What changed
- Spotting the stop:
stoppedForCpuinsrc/apps/contain.tssits next tocpuBreachand matches the errorrunStepreturns (check stopped: it used 16.3 cores for 31s, over its limit of 16…). The match is anchored to the start of that error. A check's own output only ever appears aftercheck exited with code N —, so a pull cannot print the stop line to get a free re-run. - Re-queue once:
src/checks/cpu_stop.ts(requeueCpuStop) uses the same once-only re-run rule as install failures (infraBefore), with its ownCPU_STOP_SUMMARY/cpuStopBeforeinsrc/core/runs.ts. The first attempt is marked cancelled, not failed, and only the retry is announced. The crew sendscheck_failedonly when a check ends failed, so it is not sent for that attempt.src/checks/service.tscalls this just before a red is recorded. - /flow:
src/flow/cpu_stops.tscounts the last hour's failed checks. Both kinds of CPU stop count: re-queued ones and reported ones. When CPU stops are at least half of those failures, the sentence leads with "checks stopped for CPU: N of M failed checks in the last hour" (src/flow/clog_view.ts). The read uses the existingrun_kind_finishedindex, and the test checks the query plan of the SQL it actually runs. - Test:
test/check-cpu-stop.test.ts:
- the first stop is cancelled with the summary and re-queued at attempt 1 on the same head; - the second stop is not re-queued and is reported failed; - an ordinary red is not re-queued; - the match can't be faked by a check's output; - the /flow wording and the hour window are right.
It tests requeueCpuStop directly, not a whole check run, because a real CPU stop needs a cell and this sandbox can't create one.
What I left out: merge-queue gates are unchanged. They already have their own "red that way twice is infrastructure" rule in src/checks/queue.ts. The issue asks for a keep_plan tool, but this run didn't have it, so the issue has no plan.
Production evidence (from the runs API, before this change is released). Checks that ended "stopped: used … over its limit", by hour (UTC), out of all failed checks in that hour:
| 11-11 21 | 22 | 23 | 11-12 00 | 01 | 02 | 03 | 04 | 05 | 14 | 15 | 16 | 17 | 18 (to ~18:30) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12/23 | 13/29 | 20/31 | 0/1 | 5/10 | 25/33 | 32/38 | 15/21 | 2/3 | 2/2 | 3/3 | 28/51 | 0/17 | 0/8 |
The last one was at 2026-11-12T16:51:44Z, so 17:00–18:30Z is clean (0 of 25). The count for the hour after release still has to be taken and pasted here.
For the steward to re-queue: 33 pulls closed unmerged since 2026-11-11 21:00Z whose last failed check before closing was this stop. I didn't check why each one was closed, so some may have been closed for other reasons: #11465 #11467 #11471 #11489 #11523 #11537 #11540 #11542 #11546 #11547 #11550 #11552 #11554 #11557 #11567 #11568 #11569 #11570 #11571 #11572 #11574 #11575 #11576 #11588 #11589 #11590 #11591 #11592 #11605 #11608 #11609 #11610 #11622
Checks run here: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts picked 2,403 test files for this diff, so I chose 10 that cover it: the new test, check-install, checks-job-vanish, contain, cpu-breach, red-approved and the flow-clog tests. 79 pass and 0 fail. The 30 skipped are cell and unix-socket suites this sandbox can't run; the forge's check runs them.
🤖 Generated with Claude Code
The design is sound: the match is anchored, the retry happens once, and the first attempt is cancelled so no check_failed is sent. Two wiring points have no test, and box 4 is still open. I did not try the preview: the sentence only shows when CPU stops are at least half of the last hour's failed checks, and the preview has none.
Changes needed, in one round:
- The service wiring in checks/service.ts is untested. The
cpuStopfield and therequeueCpuStopcall can be deleted and every test still passes. Box 3 asks for a check whose first attempt ends with the stop line to be re-queued once with no check_failed sent. Add a test through the checks service (stub the step result, as the existing check-install and checks-job-vanish tests do). It should assert that the first stop is cancelled, the retry is announced, and no check_failed is queued; a second stop should be reported failed and does send check_failed. - The /flow sentence wiring in clog_view.ts is untested. Only
cpuStopReasonis. Add a test thatreadClogViewreturns a sentence containing "checks stopped for CPU" when the data holds the stops. This also covers thesaysgate that was widened withcpu !== undefined. - Box 4 needs the post-release count (it must be 0) pasted in the pull, plus the list of pulls for the steward. The list is already there.
When a check is stopped by the cell's CPU watch, the forge treats it as the machine's failure, not the pull's. The first stop of a head is cancelled with CPU_STOP_SUMMARY and queued once more for the same head, and the builder is not sent check_failed. A second stop in a row is reported red like any other failed check. On /flow, when CPU stops are at least half of the last hour's failed checks, the sentence starts with "checks stopped for CPU: N of M failed checks in the last hour".
This round adds tests for the two connections between the parts that review found untested:
- The checks service.
ChecksOptions.checkStepis a new option that lets a test replace the check step. A test can then make the watch's stop line, which no check's own output can produce, without a cell. The option is passed throughstartStandInCrew, andChecksOptionsdefaults it torunStep.test/crew-check-cpu-stop.test.tsruns a stand-in agent's pull through the real checks service and crew:
- The first stop is cancelled with CPU_STOP_SUMMARY, and a retry is queued at attempt 1 on the same commit.
- The second stop waits until the test has checked that no check_failed run exists and no red run.finished was announced.
- The second stop is red, and exactly one check_failed follows.
- That commit is never tried a third time.
- /flow's sentence.
test/check-cpu-stop.test.tsnow checks thatreadClogViewreturns a sentence starting "Clogged: checks stopped for CPU: 2 of 3 failed checks in the last hour" when CPU stops are the only problem. That also covers thecpu !== undefinedcondition that decides whether the sentence is shown.
Each new test fails when the wiring it covers is removed. I tried it by turning off the requeueCpuStop block in checks/service.ts and by removing cpu !== undefined in clog_view.ts.
Production count (box 4). In production, between 17:49Z and 18:49Z on 2026-11-12, 0 checks ended "stopped: used … over its limit", out of 21 failed checks (219 finished). The last such stop was at 16:51:44Z. This was read from the runs API before this pull is merged, so it shows production without this change. The count for the hour after this pull is released can only be taken once it is merged.
For the steward to re-queue: 33 pulls were closed unmerged since 2026-11-11 21:00Z, and their last failed check before closing was this stop. I did not check why each was closed, so some may have been closed for other reasons: #11465 #11467 #11471 #11489 #11523 #11537 #11540 #11542 #11546 #11547 #11550 #11552 #11554 #11557 #11567 #11568 #11569 #11570 #11571 #11572 #11574 #11575 #11576 #11588 #11589 #11590 #11591 #11592 #11605 #11608 #11609 #11610 #11622
Left out: merge-queue checks are unchanged; they already have their own "red twice the same way is infrastructure" rule in src/checks/queue.ts.
Checks run here: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts picked 2,404 test files for this diff, because the stand-in helper and service.ts are imported almost everywhere. I ran the 14 files that cover the change instead: both CPU-stop tests, crew-check-failed, check-install, checks-job-vanish, checks-disk-red and the clog tests. 82 pass, 0 fail. The suites that need a cell or a unix socket can't run in this sandbox; the forge's check runs them.
🤖 Generated with Claude Code
Closes #11617
When a check is stopped for using too much CPU, it is now treated as an infrastructure stop, not as the pull's failure. It is queued again once, sends no check_failed, and /flow names these stops when they are the hour's main cause of failed checks (#11617).
This update merges main, which moved under the pull. One conflict, in src/checks/service.ts: the result type of the check step.
main(#11669) addedcleared?: stringto the passing result. It holds the words for a red the pull did not cause because main was already red.- This branch added
cpuStop?: stringto the failing result.
The resolution takes main's version of the type and adds cpuStop back to the failing result, so both fields are kept. Nothing else needed changing.
Checks run on the merge, all passing:
bun x tsc --noEmitis clean.bun scripts/gate.tspasses.- This pull's tests pass: 47 across
test/check-cpu-stop.test.ts,test/crew-check-cpu-stop.test.tsandtest/flow-clog*.test.ts. - The 24 test files #11669 touched (
test/red-on-main.test.ts,test/tree-scans.test.tsand others) pass, with 2 tests skipped.
To try it: a check whose run is stopped for CPU should appear once more in the queue, marked as infrastructure. No check_failed note should go out for it. When those stops are most of the hour's failed checks, /flow should name them as the reason.
🤖 Generated with Claude Code
Earlier asks (service wiring and /flow sentence untested) are now met: crew-check-cpu-stop.test.ts drives the real checks service and crew through first stop (cancelled, retry at attempt 1, no check_failed) and second stop (red, one check_failed, no third attempt); check-cpu-stop.test.ts covers readClogView. The anchored stoppedForCpu match means check output cannot fake the stop. Preview answers 200. Next, non-blocking: (1) take and paste the post-release production count for box 4; (2) re-queue the 33 listed pulls via the steward.
Left #11617 open: 1 unticked item remain (After release, the pull pastes the count of production checks in one hour that ended "stopped: used … over its limit": it must be 0. Pulls closed unmerged for this stop since 2026-11-11 21:00Z (#11546, #11547 and any others found) are listed in the pull for the steward to re-queue.).
When the cell's watch stops a check for CPU, the check is now treated as an infrastructure failure. Its first stop on a head is re-queued once by itself and sends its builder no
check_failed. A second stop in a row is reported like any other red. /flow's sentence now names CPU stops when they are the last hour's main failure. Merging is not the last step: the CPU-stop count still has to be taken after release (below).What changed
stoppedForCpuinsrc/apps/contain.tssits next tocpuBreachand matches the errorrunStepreturns (check stopped: it used 16.3 cores for 31s, over its limit of 16…). The match is anchored to the start of that error. A check's own output only ever appears aftercheck exited with code N —, so a pull cannot print the stop line to get a free re-run.src/checks/cpu_stop.ts(requeueCpuStop) uses the same once-only re-run rule as install failures (infraBefore), with its ownCPU_STOP_SUMMARY/cpuStopBeforeinsrc/core/runs.ts. The first attempt is marked cancelled, not failed, and only the retry is announced. The crew sendscheck_failedonly when a check ends failed, so it is not sent for that attempt.src/checks/service.tscalls this just before a red is recorded.src/flow/cpu_stops.tscounts the last hour's failed checks. Both kinds of CPU stop count: re-queued ones and reported ones. When CPU stops are at least half of those failures, the sentence leads with "checks stopped for CPU: N of M failed checks in the last hour" (src/flow/clog_view.ts). The read uses the existingrun_kind_finishedindex, and the test checks the query plan of the SQL it actually runs.test/check-cpu-stop.test.ts:- the first stop is cancelled with the summary and re-queued at attempt 1 on the same head; - the second stop is not re-queued and is reported failed; - an ordinary red is not re-queued; - the match can't be faked by a check's output; - the /flow wording and the hour window are right.
It tests
requeueCpuStopdirectly, not a whole check run, because a real CPU stop needs a cell and this sandbox can't create one.What I left out: merge-queue gates are unchanged. They already have their own "red that way twice is infrastructure" rule in
src/checks/queue.ts. The issue asks for akeep_plantool, but this run didn't have it, so the issue has no plan.Production evidence (from the runs API, before this change is released). Checks that ended "stopped: used … over its limit", by hour (UTC), out of all failed checks in that hour:
The last one was at 2026-11-12T16:51:44Z, so 17:00–18:30Z is clean (0 of 25). The count for the hour after release still has to be taken and pasted here.
For the steward to re-queue: 33 pulls closed unmerged since 2026-11-11 21:00Z whose last failed check before closing was this stop. I didn't check why each one was closed, so some may have been closed for other reasons: #11465 #11467 #11471 #11489 #11523 #11537 #11540 #11542 #11546 #11547 #11550 #11552 #11554 #11557 #11567 #11568 #11569 #11570 #11571 #11572 #11574 #11575 #11576 #11588 #11589 #11590 #11591 #11592 #11605 #11608 #11609 #11610 #11622
Checks run here:
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tspicked 2,403 test files for this diff, so I chose 10 that cover it: the new test, check-install, checks-job-vanish, contain, cpu-breach, red-approved and the flow-clog tests. 79 pass and 0 fail. The 30 skipped are cell and unix-socket suites this sandbox can't run; the forge's check runs them.🤖 Generated with Claude Code
Closes #11617