Repository navigation
supervisor-import-graph test probes host-lifecycle.js three times; test 3 runs two cold probes in one 30 s budget and times out under load #4408
Description
Summary
packages/coding-agent/test/supervisor-import-graph.test.ts starts five cold Node processes per file run, and three of them probe the same entry (dist/modes/rpc/host-lifecycle.js). The test "keeps the supervisor program's own graph to the supervisor plus its preamble" (:130) starts two of those probes back to back inside one 30 s test budget. On a heavily loaded host each probe takes 10-12 s, so that one test times out even though the graph it checks is correct.
Reproduction
bun run buildin a senpi checkout.- Run the file under contention, for example on a 40-core Linux runner at a load average of about 93:
cd packages/coding-agent && CI=1 bunx vitest run --reporter=verbose test/supervisor-import-graph.test.ts.
Observed on such a host (evidence held locally; trimmed):
✓ stays inside its module budget ... 12389ms
✓ gives the bundled supervisor its own program ... 2438ms
× keeps the supervisor program's own graph to the supervisor ... 36445ms
→ Test timed out in 30000ms.
✓ keeps the CLI argument parser and the provider catalog out ... 10431ms
On an idle machine the same commit passes 4/4 in about 2 s (test 3: 0.5-1.5 s). The PR head that showed the failure and its merge-base both pass there, so this is the cost of the probes under load, not a graph regression.
Expected (ideal state)
Each entry's import graph is probed once per file run, and every test reads the shared result. Test 3 then starts one probe of its own (host-supervisor-entry.js) plus the shared host-lifecycle.js result, not two cold processes. The 30 s budget then bounds one probe, not two in sequence. No assertion changes, and the timeout is not raised.
Actual
test/supervisor-import-graph.test.ts:103-105(test 1),:131-139(test 3, two probes) and:155(test 4) each callprobeImportGraph(repoRoot, .../host-lifecycle.js)on their own.test/helpers/esm-import-graph-probe.ts:78runs each probe as a blockingspawnSync(process.execPath, ...): a fresh Node process with a loader hook that callsappendFileSynconce per resolved and loaded module.packages/coding-agent/vitest.config.ts:17setstestTimeout: 30000.
Root cause
The same deterministic graph is computed repeatedly in separate cold processes. Under CPU contention a single probe costs a third of the test budget, and test 3 pays for two.
Scope and acceptance criteria
host-lifecycle.jsis probed at most once per file run, through a lazily computed, memoized result shared by tests 1, 3 and 4. The memo keys on the entry path, sohost-supervisor-entry.jsanddist/bundle/host-supervisor.jskeep their own single probes.- Test 3 starts exactly one new probe process.
- Every existing assertion stays byte-for-byte the same, and the test timeout is unchanged.
- Optional: the probe runs as an async
spawnawaited on the child'sexitevent (with the same bounded wait), so the vitest worker stays responsive while the child runs. - Proof: a count of probe processes per file run (for example a spy on the helper) shows 3 instead of 5, and the file passes under an artificial CPU load that made it fail before.
Related
- Seen as a red receipt row on fix(coding-agent): build forks atomically and keep a header write's prefix #4305 (unrelated to that PR's change; diff empty for the test, its helpers,
src/modes/rpcand the vitest config). - supervisor-import-graph fails on main: the RPC supervisor now loads core/process-footprint.js (#2268) #3703 and Bundled RPC host supervisors boot the whole CLI graph: ~60 MB extra per supervisor #3743 (earlier work on the same test file).
Activity