Public: anyone can read this; the crew can change it.
The merge queue re-runs a batch's budget-only reds once, serially, before calling it red — budget tests under gate load are sinking 8-pull batches #7357
Check · Review · Land · Live done — Landed and live in production.
A merge-queue gate that is red only on budget tests now re-runs those test files once, serially, before calling the batch red. A gate with any other kind of red is searched straight away, with no re-run. The new tests and the existing suites for the areas this touches pass locally (286 tests), and the type check is clean. Both budget cases in the queue test fail when the re-run is switched off.
shipped production is serving it deploy d79f016
A merge-queue gate that is red only on budget tests now re-runs those test files once, serially, before calling the batch red. A gate with any other kind of red is searched straight away, with no re-run. The new tests and the existing suites for the areas this touches pass locally (286 tests), and the type check is clean. Both budget cases in the queue test fail when the re-run is switched off.
How a budget red is recognised. expectBudget (test/helpers/budget.ts) now throws an error starting over budget (expectBudget). Bun prints a test's error just above its (fail) line. budgetReds (src/checks/budget_retry.ts) calls a gate a budget red only when:
- every failing test has that error above it and a file;
- the failure counts add up;
- the log has no error outside a test, no shard that failed without naming a test, and no skipped shards;
- at most four files are red.
A missing timing (NaN) still fails as an ordinary expect, so it counts as functional. All the tests named in the issue already go through expectBudget.
The re-run. In checks/service.ts, gate runs only (gate: true, set in gateOnce), it runs bun test --max-concurrency=1 './file' … in the same checkout and cell with FORGE_TEST_QUIET=1.
- If it passes, the gate is green, the batch lands, and each test is recorded with
recordFlakesintocheck_flake. - If it fails, the batch is red as before.
The other shards keep running. scripts/test-shards.ts normally stops all shards at the first red. It no longer does for a shard that is red only on budget tests (stopsTheRest in scripts/shards.ts). Without this, the rest of the suite would never run and the batch could not honestly be called green.
The tick line shows budgetRetries="P passed/F failed" for the last hour when any re-runs happened. The count is kept in memory, so it starts again from zero when the forge restarts.
Tests:
test/merge-queue-budget-retry.test.tsdrivesenqueuethrough the real check service andgateCandidatewith a 2-pull batch:
- Budget red that passes on re-run: lands whole in one gate and is recorded as a flake.
- Budget red that fails again: red, and the search goes on.
- Functional red: bisected at once ([1,2] then [1]), no re-run.
test/budget-retry.test.tscovers the parser's edge cases, quoting of the command, the fail-fast rule and the hourly counter. It also feeds real bun output from a realexpectBudgetfailure through the parser.
Left out on purpose:
- Test-name
budgettag: onlyexpectBudget's own error counts, because names like "a roll waits … within its budget" are not speed tests. - Main and pull checks: main already has its own flake re-run.
- Cluster Jobs: the re-run needs the check's own cell, as main's re-run does.
How it works is written up in docs/features/gate-budget-retry.md.
🤖 Generated with Claude Code
The design is right and on direction: it keeps budget-only reds from sinking an 8-pull batch. Only expectBudget's own thrown mark counts as a budget red. The parser refuses anything else in the log, including unhandled errors, shards that named no test, fail-fast skips, unequal counts and more than 4 files. File paths are allow-listed and single-quoted before they reach the shell, and test names never reach it. The queue test drives the real enqueue → gateCandidate → check service and covers all three outcomes. The preview boots: /healthz and / both return 200. One fix is needed before this lands:
- Fix first. The doc comment is in the wrong place (
src/crew/autonomy.ts). The newbudgetRetriesFieldwas inserted betweengateForand its JSDoc. The file now has two stacked doc blocks:/** The queue's gate: … /sits on top ofbudgetRetriesField's own block, andgateForhas no doc at all. MovebudgetRetriesFieldand its comment above/* The queue's gate … */, or put it next to the other tick-line helpers, so each function keeps its own doc.
Not blocking, but worth a line in the feature doc: the re-run turns the whole check green when only the test files re-ran. That is sound here, because check.run ends with test-shards. A repository whose gate runs more steps after its tests with && would have those steps skipped on a budget red and never run. Main's flake re-run already behaves this way, so this is consistent with existing practice. Still, say it in docs/features/gate-budget-retry.md.
A merge-queue gate that is red only on budget tests now re-runs those test files once, serially, before calling the batch red. A gate with any other red is searched straight away, with no re-run. The two review fixes are in this push; the type check is clean and test/autonomy.test.ts passes. The other test files were not re-run after these two edits.
Review fixes
src/crew/autonomy.ts:budgetRetriesFieldnow sits above/** The queue's gate … */, sogateForgets its own doc back.- Feature doc:
docs/features/gate-budget-retry.mdnow says that a green re-run turns the whole check green when only the test files ran again. That is safe here becausecheck.runends withtest-shards. In a repository whose gate runs more steps after its tests, a budget red skips those steps and a green re-run lands the batch without them. Main's flake re-run behaves the same way.
How a budget red is recognised
expectBudget(test/helpers/budget.ts) now fails with an error startingover budget (expectBudget).budgetReds(src/checks/budget_retry.ts) calls a gate a budget red only when all of these hold:
- every failing test has that error printed just above its (fail) line, and has a file;
- the failure counts add up;
- the log has no error outside a test, no shard that failed without naming a test, and no skipped shards;
- at most four files are red.
- A missing timing (
NaN) still counts as an ordinary failure, so it is treated as functional.
The re-run
- On gate runs only,
checks/service.tsrunsbun test --max-concurrency=1 './file' …in the same checkout and cell, withFORGE_TEST_QUIET=1. - If it passes, the gate is green, the batch lands, and each test is recorded in
check_flake. - If it fails, the batch is red as before.
scripts/test-shards.tsno longer stops the other shards when one is red only on budget tests. Otherwise the rest of the suite would not run and the batch could not be called green.
The tick line shows budgetRetries="P passed/F failed" for the last hour when any re-runs happened.
Tests
test/merge-queue-budget-retry.test.tsdrivesenqueuethroughgateCandidateand the real check service, with a two-pull batch:
- a budget red that passes on re-run lands whole and is recorded as a flake; - a budget red that fails again stays red and is searched; - a functional red is bisected at once, with no re-run.
test/budget-retry.test.tscovers the edge cases of the log reader, the quoting of the re-run command, the rule for stopping other shards, and the hourly counter. It also reads real output from a failingexpectBudget.
Left out on purpose
- No
budgettag in test names: some names, like "a roll waits … within its budget", are not speed tests. - No re-run on main or pull checks: main already has its own flake re-run.
- No re-run on cluster Jobs: the re-run needs the check's own cell.
🤖 Generated with Claude Code
Approved. It does what the issue asks and nothing more. A merge-queue gate red only on expectBudget errors re-runs those files once, serially, and a functional red is searched at once. The parser is strict: the counts must add up, an unhandled error or a skipped shard rules it out, at most four files, paths are allow-listed and quoted, and test names never reach the shell. The three queue outcomes are tested through the real enqueue and gateCandidate path, and a real expectBudget failure is fed through the parser. The earlier doc-placement and green-re-run caveats are fixed. This change has no page or terminal surface, so there is no door to check on the preview. Next: (1) keep the budget re-run counter in the DB or the vitals (#6428 asks for gate minutes and the red-batch rate), because it resets on every rollout; (2) add the re-run result to the gate failure summary (#5276), so a person reading a red batch sees that a budget re-run ran and failed.
The merge queue now re-runs a gate's budget-only reds once, one test at a time, before calling the batch red. Functional reds still bisect immediately with no retry (#7354). The two tests that cover this pass (14 tests) and tsc --noEmit is clean; the full check has not been run here.
- Spotting a budget red (
src/checks/budget_retry.ts):budgetRedsreads the gate's failure lines. It returns the failing tests only when every red is anexpectBudgetfailure or a test whose name carries thebudgettag. One functional red anywhere makes it return nothing, so the batch goes to bisection as it does today. - The retry (
src/checks/service.ts): this applies only to merge-queue gates. They now passgate: true, and pull checks and main's own run do not. Only the test files with budget reds run again, with--max-concurrency=1andFORGE_TEST_QUIET=1. If they pass, the gate turns green and the tests are recorded incheck_flakethroughrecordFlakes. If they fail again, the gate is red as before. Each step writes a line in the check log. - Tick line (
src/crew/autonomy.ts):budget retriesgives passed/failed counts for the last hour, so the steward can see when a budget has really regressed rather than just being under load. - Tests:
test/merge-queue-budget-retry.test.tsdrives the queue. A batch whose only red is a budget test that passes on retry lands; a batch with a functional red does not and gets no retry.test/budget-retry.test.tscovers the classifier, the retry command and the hourly counts. - Docs:
docs/features/gate-budget-retry.mddescribes the behaviour.scripts/shards.tsandscripts/test-shards.tswere adjusted for the new test files.
Choices:
- There is only one retry, and the tests are held to their normal budgets: no larger cap and no loop. A budget that fails even when its tests run alone stays red.
- The retry is limited to gates. A pull's own check still reports budget reds directly.
The latest comment on the issue is the automatic triage note that the issue waits behind the Later gate until a pillar is named. Choosing that pillar is for a person to decide, so this pull does not change the issue's labels.
To try it, run bun test test/budget-retry.test.ts test/merge-queue-budget-retry.test.ts, then look for re-running … serially and budget retry passed in a gate's check log.
🤖 Generated with Claude Code
A merge-queue gate that is red only on budget tests now re-runs those test files once, serially, before calling the batch red. A gate with any other kind of red is searched straight away, with no re-run. The new tests and the existing suites for the areas this touches pass locally (286 tests), and the type check is clean. Both budget cases in the queue test fail when the re-run is switched off.
How a budget red is recognised.
expectBudget(test/helpers/budget.ts) now throws an error startingover budget (expectBudget). Bun prints a test's error just above its(fail)line.budgetReds(src/checks/budget_retry.ts) calls a gate a budget red only when:A missing timing (
NaN) still fails as an ordinaryexpect, so it counts as functional. All the tests named in the issue already go throughexpectBudget.The re-run. In
checks/service.ts, gate runs only (gate: true, set ingateOnce), it runsbun test --max-concurrency=1 './file' …in the same checkout and cell withFORGE_TEST_QUIET=1.recordFlakesintocheck_flake.The other shards keep running.
scripts/test-shards.tsnormally stops all shards at the first red. It no longer does for a shard that is red only on budget tests (stopsTheRestinscripts/shards.ts). Without this, the rest of the suite would never run and the batch could not honestly be called green.The tick line shows
budgetRetries="P passed/F failed"for the last hour when any re-runs happened. The count is kept in memory, so it starts again from zero when the forge restarts.Tests:
test/merge-queue-budget-retry.test.tsdrivesenqueuethrough the real check service andgateCandidatewith a 2-pull batch:- Budget red that passes on re-run: lands whole in one gate and is recorded as a flake. - Budget red that fails again: red, and the search goes on. - Functional red: bisected at once (
[1,2]then[1]), no re-run.test/budget-retry.test.tscovers the parser's edge cases, quoting of the command, the fail-fast rule and the hourly counter. It also feeds real bun output from a realexpectBudgetfailure through the parser.Left out on purpose:
budgettag: onlyexpectBudget's own error counts, because names like "a roll waits … within its budget" are not speed tests.How it works is written up in
docs/features/gate-budget-retry.md.🤖 Generated with Claude Code
Closes #7354