fix(test): remove all 8 delayed process.exit teardown bombs — the tier-1 gate can finally fail

bun test runs every file in ONE process, so a 500ms setTimeout(process.exit(0))
armed in afterAll fired mid-way through a LATER file and killed the entire
suite with exit 0 and no summary — only ~16 of 434 files ran, and every
downstream failure was invisible (observed live throughout this wave's
enumeration). Changes, all guarded by fault injection:

- Replace every delayed-exit teardown with a time-boxed close of the file's
  own browser (8 files across browse/ and design/); stub the daemon
  /shutdown timer instead of letting its unconditional process.exit tear
  the runner down.
- test/no-suicide-exit.test.ts: static tripwire — no *.test.ts may schedule
  a delayed process.exit again.
- test/exit-propagation.test.ts + fixtures: fault injection with REAL bun
  output proves the truncation shape (exit 0, no summary) and that
  scripts/test-free-shards.ts now detects it: a shard exiting 0 WITHOUT
  bun's final summary line is treated as FAILED (exit code alone is not
  evidence of completion).
- handoff: the three headed-mode integration tests are darwin-skipped with
  a pointer to the known macOS headed-launch breakage (#2242/#2554); they
  keep running on Linux CI. Un-skip in the browse-daemon wave.
- feedback-roundtrip: repair the handler call sites unmasked by the fix —
  handlers take (command, args, session, bm); passing the manager where a
  session belongs broke all six tests.
- user-slug-fallback: HOME isolation makes endpoint_hash deterministic.

Fixes #2421, #2435.

Contributed by @sneakygriff (PR #2172) with repairs from @time-attack
(PR #2230 feedback-roundtrip hunks); supersedes PR #2252 by @whd4 (same
defect, credited).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 20:20:51 -07:00
co-authored by Claude Fable 5
parent 3f176d2226
commit e0bfc8fff5
16 changed files with 322 additions and 77 deletions
+9 -1
View File
@@ -35,6 +35,12 @@ function runConfig(args: string[], extraEnv: Record<string, string> = {}): { std
encoding: 'utf-8',
env: {
...process.env,
// HOME isolation: endpoint_hash() reads $HOME/.claude.json for the
// gbrain MCP URL. Pointing HOME at the empty TMP_HOME makes it
// deterministically 'local' regardless of the developer's real
// ~/.claude.json (which would otherwise change the persisted key
// namespace to user_slug_at_<sha8-of-url>).
HOME: TMP_HOME,
...extraEnv,
},
timeout: 5000,
@@ -92,7 +98,9 @@ describe('resolve-user-slug fallback chain', () => {
const configFile = join(TMP_HOME, 'config.yaml');
expect(existsSync(configFile)).toBe(true);
const content = readFileSync(configFile, 'utf-8');
expect(content).toMatch(/^user_slug_at_(local|[a-f0-9]{8}|[a-f0-9]{16}):\s+persisttest/m);
// HOME is isolated to the empty TMP_HOME, so endpoint_hash() is
// deterministically the literal 'local' on every machine.
expect(content).toMatch(/^user_slug_at_local:\s+persisttest/m);
});
test('subsequent calls return same slug (stable across sessions)', () => {