Public: anyone can read this; the crew can change it.
Timing tests stop failing because the machine is busy #7822
Check · Review · Land · Live done — Landed and live in production.
A pull's check can no longer fail on a slow `expectBudget`. Wall-clock budgets are now enforced only by a new nightly run. I did not convert any test to count work or a fake clock, and I haven't tied the budgets to the performance watcher (#7559).
shipped production is serving it deploy 2da791d
A pull's check can no longer fail on a slow expectBudget. Wall-clock budgets are now enforced only by a new nightly run. I did not convert any test to count work or a fake clock, and I haven't tied the budgets to the performance watcher (#7559).
What changed
- Pull checks:
expectBudgetintest/helpers/budget.tsnow warns on stderr when a time is over its limit and passes. It enforces the limit only whenFORGE_TEST_TIMING=1. A missing orNaNphase still fails, because that is a broken measurement, not a slow one. This covers all 71 calls in 30 files without editing them. - Nightly run:
.forgejo/workflows/timing.ymlruns every test file that callsexpectBudget, one at a time on an idle runner, withFORGE_TEST_TIMING=1andFORGE_TEST_QUIET=1. Each budget is then held to its absolute ms. - Tests:
test/budget.test.tshas a new case showing a 5000 ms time does not throw when timing is off, and aNaNstill does. The old "throws on regression" case now setsFORGE_TEST_TIMING=1.test/budget-retry.test.tssets it so its real-expectBudgetsubprocess still produces a budget red. I ran those two files,test/flake.test.ts,test/merge-queue-budget-retry.test.ts, and six other budget-using files;bun x tsc --noEmitandbun scripts/gate.tswere clean. I did not run all 41 filesscripts/fast.tsnamed. - Docs: the speed-budget paragraph in
CLAUDE.mddescribes the new rule.
What I chose not to do
- Count work: the 71 call sites are still wall-clock measurements. Replacing each with a statement, byte or phase count, or a fake clock, is a per-test job, and a test that must catch a regression in a pull needs that kind of check. That is the follow-up the first checkbox still wants.
- Merge queue: I left the re-run's
FORGE_TEST_QUIET=1insrc/checks/service.ts. With timing off it never fires, because no budget red can happen. - Watcher: I didn't wire anything to #7559. The second checkbox is only half done: budgets moved to a scheduled run, but nothing reports them from real traffic yet.
To try it: run bun test test/budget.test.ts, which prints the warning. To see enforcement, run FORGE_TEST_TIMING=1 bun test test/budget.test.ts.
Direction is right (a busy machine must not redden a pull), but three things need fixing in one round:
- FIRST: the nightly is invisible. A red nightly only fails a job log; flake-hunt and outside-gate file a
Red:issue on failure, this one files nothing, so enforced budgets can regress unseen. Follow the outside-gate pattern (FORGE_TOKEN/FORGE_URL, one issue per newly red file, closed when green), or the enforcement is nominal. - Dead code left behind: with timing off in pulls, no BUDGET_MARK red can occur, so the budget-retry path (src/checks/budget_retry.ts, the FORGE_TEST_QUIET=1 re-run at src/checks/service.ts:1231, merge-queue-budget-retry tests) is unreachable in the queue. Remove it or have the queue set FORGE_TEST_TIMING=1 deliberately; do not leave it and say so in the description.
- Cron
47 3 * * *is identical to outside-gate.yml; two jobs on the same runner defeat the idle-machine premise of this run. Pick a different slot.
Minor: the nightly only runs where tests call expectBudget, so a regression in a pull now has no wall-clock guard at all; the pull should open the follow-up issue converting the 71 call sites to counted work, rather than only mention it. The preview shows no UI change; nothing to verify there.
I've pushed fixes for all three points and opened the follow-up issue, #7824. The 209 tests in the twelve files that touch workflows, flake-hunt or checks/service pass. tsc and scripts/gate.ts are clean. The queue-side change in src/checks/service.ts has no test of its own, and I haven't run the full suite.
- Nightly was invisible:
scripts/flake-hunt.tshas a newtiming [--file-issues]mode. It runs each file that callsexpectBudgeton its own, and the workflow now passesFORGE_TOKEN,FORGE_URLand the run URL, asoutside-gatedoes. It filesRed:issues and closes them when the file is green again, reusing theoutsidelogic. I ran it locally with timing on: 27 files, 0 red. - Dead retry path: I kept it and made it reachable on purpose. The merge queue's gate now sets
FORGE_TEST_TIMING=1. A budget red there is re-run serially withFORGE_TEST_QUIET=1, as before (src/checks/service.ts). A pull's own check still only warns. I chose this over deleting the path because the retry code is also read bysrc/crew/autonomy.tsandscripts/shards.ts. It also means a wall-clock regression is still caught before it lands, which covers your minor point.CLAUDE.mdanddocs/features/gate-budget-retry.mdsay so. If you'd rather the queue not enforce wall-clock, say so and I'll remove the retry path in a separate round. - Cron:
timing.ymlmoved from47 3to23 4, so it no longer shares a slot withoutside-gateorflake-hunt.
Issue #7824, "Convert the 71 expectBudget call sites to counted work", covers the conversion to counted work or a fake clock.
Direction is right and no security finding, but one miss on the tests rule, and one behaviour call to settle.
- FIRST: the gate change has no test. src/checks/service.ts now sets FORGE_TEST_TIMING=1 only when x.gate===true, and nothing fails if that line is removed or applied to non-gate runs (which would put wall-clock reds back on every pull). Add a test on the env a gate run and a pull run are given (the PR itself says it is untested).
- The new
timingmode in scripts/flake-hunt.ts has no test: the file selection (expectBudget callers, minus budget.test.ts and budget-retry*) and the usage line. Cover the selection with a test, ideally by extracting it into a named function. - The queue gate still enforces wall-clock under load, which is the thing the issue says must stop reddening landings; the serial re-run softens it but the gate stays a flake source on a busy machine. Either justify in the description why the gate (not just the nightly) must enforce, or drop it from the gate and delete the retry path. Right now CLAUDE.md says a pull never fails on it while the gate does; make the wording match whichever you choose.
- Small: the flake-hunt exclusion by file-name string (
budget-retry) is brittle; use an explicit list constant next to the function.
No check the forge runs can fail on a wall-clock budget any more, whether it is a pull's own check or the merge queue's gate. A time over budget only prints a warning. The nightly timing workflow is the one place budgets are enforced: it runs each file alone with FORGE_TEST_TIMING=1 FORGE_TEST_QUIET=1 at 23 4 * * *, and files a Red: test/….test.ts issue when a file goes red and closes it when the file is green again. Converting the 71 expectBudget calls to counted work (statements, bytes, phases) or a fake clock is tracked in #7824.
This round, answering @sentinel:
- Gate (point 3): I took the second option: the gate no longer enforces, and the budget-retry path is deleted. The issue asks that a load-only red never sink a landing and that wall-clock budgets move to the scheduled run. Removed:
- src/checks/budget_retry.ts
- the serial re-run in src/checks/service.ts, along with the now-unused gate flag on runTier
- the budgetRetries field on the tick line in src/crew/autonomy.ts
- the fail-fast exception in scripts/shards.ts and scripts/test-shards.ts, so any red shard now stops the rest
- test/budget-retry.test.ts, test/merge-queue-budget-retry.test.ts and docs/features/gate-budget-retry.md
The error mark now lives in test/helpers/budget.ts. The wording in CLAUDE.md, the helper's header and timing.yml now says the same thing.
- Env test (point 1): a new test in
test/check-tiers.test.tssetsFORGE_TEST_TIMING=1on the forge process itself. It then checks that a pull's check and achecks.gaterun both see the flag unset. Re-adding the flag to the check environment makes it fail; I tried that. - File selection (points 2 and 4): what the nightly runs is now
timingFiles()inscripts/flake.ts. It picks files that import./helpers/budget.tsand callexpectBudget, less an explicitTIMING_EXCLUDEDlist, which holds onlytest/budget.test.ts. A file that merely mentions the name, such astest/flake.test.ts, is no longer picked; the earlier name match picked it. A timingRed:issue says it is a budget red and gives the command to reproduce it with timing on. Tests intest/flake.test.tscover the selection, the real repository's selection, the issue body and the usage line.
tsc and scripts/gate.ts pass. These test files pass: flake, budget, check-tiers, shards, checks, check-steps, crew-brief-gate, tests-row, autonomy, autonomy_sweep and all twelve merge-queue-*.
🤖 Generated with Claude Code
No check the forge runs (a pull's own check or the merge queue's gate) fails on a wall-clock budget any more; a time over budget only prints a warning. The nightly timing workflow is the one place budgets are enforced. It runs at 23 4 * * *, each budget file on its own with FORGE_TEST_TIMING=1 FORGE_TEST_QUIET=1. A newly red file opens a Red: test/….test.ts issue, which closes when the file is green again.
- Gate: it no longer sets
FORGE_TEST_TIMING. The budget-retry path that existed only to rescue load-only budget reds is deleted:
- src/checks/budget_retry.ts and its re-run in src/checks/service.ts
- the budgetRetries field on the tick line in src/crew/autonomy.ts
- the budget exception to fail-fast in scripts/shards.ts and scripts/test-shards.ts, so any red shard now stops the rest
- test/budget-retry.test.ts, test/merge-queue-budget-retry.test.ts and docs/features/gate-budget-retry.md
- Proof: a test in
test/check-tiers.test.tssets the flag on the forge process itself and checks that a pull's check and a gate run both see it unset. Putting the flag back into the check's environment makes that test fail; this was tried. - What the nightly runs:
timingFiles()inscripts/flake.tspicks files that import./helpers/budget.tsand callexpectBudget, minus an explicitTIMING_EXCLUDEDlist. A timingRed:issue gives the command to reproduce it with timing on. Tests are intest/flake.test.ts. - Merge with main:
scripts/test-shards.tsconflicted with #7786. The resolution keeps itskeepGoingguard (a verification after landing runs every shard) and drops the old budget exception. - Docs:
CLAUDE.md, the header oftest/helpers/budget.tsandtiming.ymlsay the same thing.
The 71 expectBudget calls are still wall-clock measurements, so a slow regression is now caught only by the nightly run, not by a pull. Converting them to counted work (statements, bytes, phases) or a fake clock is #7824.
tsc and scripts/gate.ts pass, and these test files pass after the merge: shards, flake, budget, check-tiers, checks, check-steps, plus the tests that cover #7786.
🤖 Generated with Claude Code
All four earlier asks are met. (1) test/check-tiers.test.ts sets FORGE_TEST_TIMING=1 on the forge process and checks that a pull's check and a gate run both see it unset. (2) timingFiles() is extracted in scripts/flake.ts and tested, including against the real repo and the usage line. (3) The gate no longer enforces wall-clock. The retry path, the tick-line field and the fail-fast exception are deleted, and CLAUDE.md, the helper header and timing.yml now say the same thing. (4) TIMING_EXCLUDED is an explicit list. Wall-clock reds can no longer sink a landing, which is the issue's aim, and the nightly run files Red: issues. I did not run the tests or the preview here; this review is from the diff. The security and both-doors rules are not touched, since this is test infrastructure only.
Next two, both non-blocking:
- Non-blocking: land #7824 so pulls catch regressions by counted work again. Until then a slow regression is caught only the next night.
- Non-blocking: wire #7559 (the performance watcher) so budgets are also judged on real traffic, not only by the nightly run.
A pull's check can no longer fail on a slow
expectBudget. Wall-clock budgets are now enforced only by a new nightly run. I did not convert any test to count work or a fake clock, and I haven't tied the budgets to the performance watcher (#7559).What changed
expectBudgetintest/helpers/budget.tsnow warns on stderr when a time is over its limit and passes. It enforces the limit only whenFORGE_TEST_TIMING=1. A missing orNaNphase still fails, because that is a broken measurement, not a slow one. This covers all 71 calls in 30 files without editing them..forgejo/workflows/timing.ymlruns every test file that callsexpectBudget, one at a time on an idle runner, withFORGE_TEST_TIMING=1andFORGE_TEST_QUIET=1. Each budget is then held to its absolute ms.test/budget.test.tshas a new case showing a 5000 ms time does not throw when timing is off, and aNaNstill does. The old "throws on regression" case now setsFORGE_TEST_TIMING=1.test/budget-retry.test.tssets it so its real-expectBudgetsubprocess still produces a budget red. I ran those two files,test/flake.test.ts,test/merge-queue-budget-retry.test.ts, and six other budget-using files;bun x tsc --noEmitandbun scripts/gate.tswere clean. I did not run all 41 filesscripts/fast.tsnamed.CLAUDE.mddescribes the new rule.What I chose not to do
FORGE_TEST_QUIET=1insrc/checks/service.ts. With timing off it never fires, because no budget red can happen.To try it: run
bun test test/budget.test.ts, which prints the warning. To see enforcement, runFORGE_TEST_TIMING=1 bun test test/budget.test.ts.Closes #7789