bun test — the documented Tier 1 gate — produces different results run to run on an unmodified clone. Same command, same tree, no changes in between: 3 failures, then 0.
Reproduce (pristine v1.61.0.0, no modifications)
git clone --depth 1 https://github.com/garrytan/gstack.git && cd gstack
bun install
bun test browse/test/ test/ make-pdf/test/ \
--ignore 'test/skill-e2e-*.test.ts' --ignore test/skill-llm-eval.test.ts \
--ignore test/skill-routing-e2e.test.ts --ignore test/codex-e2e.test.ts \
--ignore test/gemini-e2e.test.ts
(That's the test script from package.json, minus the trailing slop:diff.)
Run 1 — 3 failures:
(fail) --list > handles missing proposals file [5006.61ms]
(fail) gstack-community-dashboard > connects to Supabase when config exists [6887.86ms]
(fail) gstack-gbrain-install D5 detect-first > --dry-run falls through to fresh clone
when no valid clone detected [7388.56ms]
Run 2, identical command, nothing changed — 0 failures.
A third run of just those three files together produced yet another failing set — only gstack-gbrain-install D5 detect-first > rejects a pre-existing path that lacks a valid gbrain package.json, with the other two green.
Each file is deterministic in isolation
Two runs each, same clone:
test/distill-apply.test.ts 11 pass 0 fail (x2)
test/telemetry.test.ts 40 pass 0 fail (x2)
test/gbrain-detect-install.test.ts 16 pass 0 fail (x2)
So the tests themselves are fine. Something about running the suite together isn't.
What it isn't
Not shared-state pollution — at least not for distill-apply, which is hermetic:
beforeEach(() => {
stateRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-apply-'));
...
});
afterEach(() => { fs.rmSync(stateRoot, { recursive: true, force: true }); });
// and in run():
env.GSTACK_STATE_ROOT = stateRoot;
delete env.GSTACK_HOME;
Fresh temp dir per test, state root redirected into it, GSTACK_HOME stripped from the child env. Another test writing to ~/.gstack can't reach it.
Not network or missing config — handles missing proposals file asserts on a hermetic temp dir and touches nothing external.
Hypothesis (not proven)
All three spawn subprocesses via spawnSync, and every failure carried a multi-second duration — 5.0s, 6.9s, 7.4s — against sub-second in isolation. Under bun's concurrent file execution, dozens of files each spawning children may be saturating the machine enough for these to time out or for a spawn to fail.
I'm flagging that as a guess. What I've established is the non-determinism and that it isn't state or environment; I haven't proven the contention mechanism.
Worth noting one failure was Expected: 0, Received: 3 — a real exit code from the binary, not an obvious timeout artifact. So contention may not explain all of them.
Why it matters
CONTRIBUTING lists bun test as the free gate that "runs on every commit." A gate that fails ~randomly trains people to re-run until green, which is how a real regression gets waved through. It also cost me a while on an unrelated PR (#2531) working out whether I'd broken something.
Possible directions
- Run the subprocess-spawning files serially (bun's
--concurrency or splitting them into their own pass)
- Give the
spawnSync calls explicit generous timeouts so contention produces a clear timeout rather than a confusing assertion failure
- Capture
stderr/status in the failure message on those assertions, so the next person sees why the child failed
Happy to take a crack at any of these if you have a preference.
Environment: macOS (Darwin 25.5.0), bun 1.3.13, pristine clone at 94993f7.
Related: #2532 (a separate, deterministic isolation problem in test/host-config.test.ts).
bun test— the documented Tier 1 gate — produces different results run to run on an unmodified clone. Same command, same tree, no changes in between: 3 failures, then 0.Reproduce (pristine
v1.61.0.0, no modifications)(That's the
testscript frompackage.json, minus the trailingslop:diff.)Run 1 — 3 failures:
Run 2, identical command, nothing changed — 0 failures.
A third run of just those three files together produced yet another failing set — only
gstack-gbrain-install D5 detect-first > rejects a pre-existing path that lacks a valid gbrain package.json, with the other two green.Each file is deterministic in isolation
Two runs each, same clone:
So the tests themselves are fine. Something about running the suite together isn't.
What it isn't
Not shared-state pollution — at least not for
distill-apply, which is hermetic:Fresh temp dir per test, state root redirected into it,
GSTACK_HOMEstripped from the child env. Another test writing to~/.gstackcan't reach it.Not network or missing config —
handles missing proposals fileasserts on a hermetic temp dir and touches nothing external.Hypothesis (not proven)
All three spawn subprocesses via
spawnSync, and every failure carried a multi-second duration — 5.0s, 6.9s, 7.4s — against sub-second in isolation. Under bun's concurrent file execution, dozens of files each spawning children may be saturating the machine enough for these to time out or for a spawn to fail.I'm flagging that as a guess. What I've established is the non-determinism and that it isn't state or environment; I haven't proven the contention mechanism.
Worth noting one failure was
Expected: 0, Received: 3— a real exit code from the binary, not an obvious timeout artifact. So contention may not explain all of them.Why it matters
CONTRIBUTING lists
bun testas the free gate that "runs on every commit." A gate that fails ~randomly trains people to re-run until green, which is how a real regression gets waved through. It also cost me a while on an unrelated PR (#2531) working out whether I'd broken something.Possible directions
--concurrencyor splitting them into their own pass)spawnSynccalls explicit generous timeouts so contention produces a clear timeout rather than a confusing assertion failurestderr/statusin the failure message on those assertions, so the next person sees why the child failedHappy to take a crack at any of these if you have a preference.
Environment: macOS (Darwin 25.5.0), bun 1.3.13, pristine clone at
94993f7.Related: #2532 (a separate, deterministic isolation problem in
test/host-config.test.ts).