You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CI: run-with-stall-guard kills a HEALTHY solo tail task — turbo grouped log order flushes nothing while one long task runs, and cli:test now outgrows the 10-minute silence budget #11808
Found while running the patch round on PR #11785 (issue #11635): Test Core (1/6) failed twice, identically, with exit 75 — and the diff is not the cause. Filed unassigned; the remedy choice (see options) is a PM/maintainer call because it changes CI mechanism for every shard.
The occurrence (run 32750890397, job 97507407281, both attempts)
The stall guard's own banner, from the job log:
⛔ STALL: no test output for 10.0 minutes (limit: 10m).
frozen since : 2026-08-24T16:31:40.939Z
last line : ::endgroup::
That ::endgroup:: is @objectstack/cli:build finishing. From that moment @objectstack/cli:test was the ONLY task still running on the shard — and the step printed zero bytes until the guard killed the process group at 16:41:42 and exited 75 (STALL_EXIT_CODE, scripts/run-with-stall-guard.mjs).
The suite underneath was healthy. Turbo on CI uses grouped log order — a task's output is flushed only when the task ENDS — so when the kill forced the flush, the buffered @objectstack/cli:test group came out full of passing tests with internal timestamps running 16:32:12 → 16:40:50, i.e. continuous activity until 52 seconds before the kill, zero ✗, no vitest failure summary (killed mid-run → pnpm ELIFECYCLE, turbo 62 successful, 63 total). The guard's SIGUSR2 forensics agree: only pnpm + turbo were in the signalled group, both "event loop responsive … awaiting something" — turbo waiting on its running child.
Control: the same task on the same merged tree in a worktree — pnpm exec turbo run test --filter=@objectstack/cli — passes completely: 173 files, 1981 tests, exit 0, wall 11m50s.
@objectstack/cli:test has outgrown the budget.scripts/test-shard-timings.json (measuredAt 2026-08-24 file says 2026-08-21) records cli at 392.11s; the failing run shows it alive past 601s when killed, and it measures ~710s locally. The suite has grown ~50%+ since the ledger was cut (recent e2e additions run 13-30s each).
Why PR #11785 hits it deterministically while others don't: on a driver-sql PR the affected-set sharding puts @objectstack/cli (the heaviest affected package) on a shard with four small packages; everything else finishes in ~3 minutes and cli:test runs alone past the 10-minute silence line. Any PR whose affected set composes a shard this way now lands a guaranteed red — the failure will look like "Test Core (1/6) failed twice, not a flake" on innocent diffs.
Remedy directions (not chosen here — each changes CI behaviour for every shard)
Run the shard's turbo with --log-order=stream so the guard sees real liveness (restores the pre-grouped behaviour the guard was written against; costs interleaved logs).
Or teach the guard a second liveness probe (e.g. child process CPU ticks / task-level progress) instead of raw output bytes — its forensic sampler already reads cpuΔ.
Found while running the patch round on PR #11785 (issue #11635):
Test Core (1/6)failed twice, identically, with exit 75 — and the diff is not the cause. Filed unassigned; the remedy choice (see options) is a PM/maintainer call because it changes CI mechanism for every shard.The occurrence (run 32750890397, job 97507407281, both attempts)
The stall guard's own banner, from the job log:
That
::endgroup::is@objectstack/cli:buildfinishing. From that moment@objectstack/cli:testwas the ONLY task still running on the shard — and the step printed zero bytes until the guard killed the process group at 16:41:42 and exited 75 (STALL_EXIT_CODE,scripts/run-with-stall-guard.mjs).The suite underneath was healthy. Turbo on CI uses grouped log order — a task's output is flushed only when the task ENDS — so when the kill forced the flush, the buffered
@objectstack/cli:testgroup came out full of passing tests with internal timestamps running 16:32:12 → 16:40:50, i.e. continuous activity until 52 seconds before the kill, zero✗, no vitest failure summary (killed mid-run → pnpmELIFECYCLE, turbo62 successful, 63 total). The guard's SIGUSR2 forensics agree: only pnpm + turbo were in the signalled group, both "event loop responsive … awaiting something" — turbo waiting on its running child.Control: the same task on the same merged tree in a worktree —
pnpm exec turbo run test --filter=@objectstack/cli— passes completely: 173 files, 1981 tests, exit 0, wall 11m50s.The mechanism — two defects colliding
run-with-stall-guard.mjswatches step output; under turbo's grouped log order a running task emits nothing until it ends. A shard whose tail is one task longer than--stall-minutesis therefore killed by construction, healthy or not. (CI:Test Corestalls mid-suite with frozen log output — three occurrences in one day, each costing a manual diagnosis + rerun #4250 was real stalls — frozen suites; this is the guard's instrument reading a healthy run as one. CI:Test Corestalls mid-suite with frozen log output — three occurrences in one day, each costing a manual diagnosis + rerun #4250's triage line "every stall so far passed on a plain rerun" no longer holds: this shape reproduces deterministically, and did, twice.)@objectstack/cli:testhas outgrown the budget.scripts/test-shard-timings.json(measuredAt 2026-08-24 file says 2026-08-21) records cli at 392.11s; the failing run shows it alive past 601s when killed, and it measures ~710s locally. The suite has grown ~50%+ since the ledger was cut (recent e2e additions run 13-30s each).Why PR #11785 hits it deterministically while others don't: on a driver-sql PR the affected-set sharding puts
@objectstack/cli(the heaviest affected package) on a shard with four small packages; everything else finishes in ~3 minutes and cli:test runs alone past the 10-minute silence line. Any PR whose affected set composes a shard this way now lands a guaranteed red — the failure will look like "Test Core (1/6) failed twice, not a flake" on innocent diffs.Remedy directions (not chosen here — each changes CI behaviour for every shard)
--log-order=streamso the guard sees real liveness (restores the pre-grouped behaviour the guard was written against; costs interleaved logs).cpuΔ.--stall-minutesabove the heaviest single package's runtime — weakest option: it re-opens the CI:Test Corestalls mid-suite with frozen log output — three occurrences in one day, each costing a manual diagnosis + rerun #4250 wait it was built to close, and decays again as suites grow.scripts/test-shard-timings.json— cli at 392s is ~50% under its real runtime, which also skews shard balancing.Refs
scripts/run-with-stall-guard.mjs(exit 75 = declared stall); wiring comment in.github/workflows/ci.ymlTest Core jobTest Corestalls mid-suite with frozen log output — three occurrences in one day, each costing a manual diagnosis + rerun #4250 (closed) — the real-stall family this guard was built for; A failingturbo run testtask can land in Test Core with ZERO captured output, and the completeness guard's green does not cover a package that printed nothing #10032 — adjacent but distinct (zero captured output of a failing task)Generated by Claude Code