Measured 2026-10-13/14 on main 6dfbff10: each of the five slowest packages run once with -json, niced, one at a time. No code was changed. The gate's wall clock is the slowest package, because Go runs packages side by side.
Where the time is
| Package | CI | What it is spending it on | |---|---|---| | internal/console | 370 s | One test, TestClaimConsoleMutationInvariant, is 36% of the package: 233 subtests, each building a full console fixture (about 1.2 s) and polling for quiet in 100 ms steps. Tests named Claim* are 53% in all. | | root package | 359 s | About 135 separate go builds of the carlos binary, 2.0 s each warm. buildCarlos has 48 callers and e2e_test.go repeats the same build inline at 20 sites. Nothing is shared. Roughly 270 s of the package. | | internal/activator | 227 s | Real waiting. About 30 tests call Start with a 2 s context against a fake child that never binds its socket, and wait the 2 s out. Two tests wait out real 1+2+4+8 s backoff, one a 20 s stop timeout. | | internal/host | 95 s | Off the critical path. One test makes 30,000 fsynced SQLite commits: seconds on CI's tmpfs, 16 minutes on a disk. | | cmd/moveprobe | 93 s | Off the critical path. One test waits out a real 60 s grace period. |
No test in these packages calls t.Parallel() (one in moveprobe does, to no effect). Nothing uses fixed ports; the blockers are t.Setenv, t.Chdir and saved package variables.
The local figures for console and host were inflated several times over by disk fsync (8.8 ms each on /var/tmp) and machine load, so the shares above are reliable and the CI-second estimates below are rough.
Ranked plan
| # | Change | Saves in its package | Gate effect | Risk | |---|---|---|---|---| | 1 | Root: build the carlos binary once and share it | about 220 s of 359 | none alone (console stays at 370); it is what makes #2 or #3 pay | low | | 2 | Gate: split console's tests by name across amadan shards (@N), with a fence that the union equals go test -list | console 370 to about 3 x 125 | large; the only cut to console that edits no test | medium: make test and CI stop being one definition | | 3 | Console: run the invariant test's subtests in bounded parallel, or share its fixture | about 130 s | about 130 s once #1 is in | highest: it is a safety-invariant test that measures a quiet window, and contention could hide a late effect | | 4 | Activator: stop waiting out 2 s once the fake child has recorded; give the refusal tests a real "nothing ran" signal | 55 to 65 s of 227 | only once console and root are under 227 | medium | | 5 | Activator: make the stop timeout and backoff base injectable | about 50 s | as #4 | medium; touches production constants |
Not worth doing for CI time: moveprobe, the host journal test, synchronous=OFF in fixtures.
Biggest unknown: how much of console's 370 s is its own work and how much is CPU contention from root linking 120 MB binaries beside it. #1 answers that as a side effect.
Doing now
#1, on branch test-suite-speed, stacked on test-suite-debt (both touch the root package's TestMain).
For Paul
#2 against #3 is a ruling: #2 gives up "CI is make test" (the principle written at the top of .amadan/ci.d/10-gate); #3 keeps it and touches a safety test. #4 and #5 can follow either.