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
There is no matching afterEach that calls .mockRestore() (per-spy) or mock.restore() (all mocks). When bun runs test files in the same worker process, the spy persists past lifecycle.test.ts and pollutes any downstream test that calls os.homedir(). Each new call inside the spy hits mkdtempSync again, so two calls in the same downstream test return two different random temp dirs — which is exactly what test/permission/next.test.ts observes.
Reproducibility
Passes locally in isolation (bun test test/permission/next.test.ts → 80/80). Local HOME is stable, no spy is installed.
Passes locally in isolation (bun test test/mcp/lifecycle.test.ts → clean).
Fails on CI when the full suite runs, because file order in the worker exposes the leak.
Impact
release.yml is unaffected — the release workflow runs test/branding/ + test/install/ only (see .github/workflows/release.yml:47), not the full suite, so tag builds ship green.
PR checks are affected — the required CI/TypeScript job fails on any PR whose test-run order exposes the leak, forcing an admin bypass or a re-run gamble. This bit PR release: v0.9.3 #1041 (v0.9.3 release PR).
Fix
Two clean options — pick whichever fits the file's style:
Option A — per-spy restore (explicit, mirrors the beforeEach setup):
Add a smoke test that asserts os.homedir() returns the process's real home immediately after the lifecycle.test.ts suite completes:
// packages/opencode/test/mcp/lifecycle-spy-leak.test.tsimport{test,expect}from"bun:test"importosfrom"os"test("os.homedir spy from lifecycle.test.ts must not leak",()=>{expect(os.homedir()).not.toMatch(/mcp-lifecycle-home-/)})
Or, less surgical but more general — a bun:test hook in a top-level preload that runs mock.restore() at file boundaries.
Priority
Low-medium — no production impact, no data risk. Purely a CI/DX problem: it forces PR authors to either re-run the check hoping for a favourable file order or ask for an admin bypass on unrelated PRs. Fix is small and self-contained; regression test would prevent recurrence.
Symptom
The
CI / TypeScriptjob (which runs the fullbun testsuite) fails intermittently on 6 tests inpackages/opencode/test/permission/next.test.ts:fromConfig - expands tilde to home directoryfromConfig - expands \$HOME to home directoryfromConfig - expands \$HOME without trailing slashfromConfig - expands exact tilde to home directoryevaluate - matches expanded tilde patternevaluate - matches expanded \$HOME patternFailure output shows two DIFFERENT
os.homedir()return values in the same test — one at test-execution time, one atexpect().toEqual()time:The
mcp-lifecycle-home-prefix is the smoking gun — the two paths come frommkdtempSync(path.join(tmpdir(), "mcp-lifecycle-home-")).Root cause
packages/opencode/test/mcp/lifecycle.test.ts:250installs a spy onos.homedirinbeforeEachbut never restores it:There is no matching
afterEachthat calls.mockRestore()(per-spy) ormock.restore()(all mocks). When bun runs test files in the same worker process, the spy persists pastlifecycle.test.tsand pollutes any downstream test that callsos.homedir(). Each new call inside the spy hitsmkdtempSyncagain, so two calls in the same downstream test return two different random temp dirs — which is exactly whattest/permission/next.test.tsobserves.Reproducibility
bun test test/permission/next.test.ts→ 80/80). LocalHOMEis stable, no spy is installed.bun test test/mcp/lifecycle.test.ts→ clean).Impact
release.ymlis unaffected — the release workflow runstest/branding/ + test/install/only (see.github/workflows/release.yml:47), not the full suite, so tag builds ship green.CI/TypeScriptjob fails on any PR whose test-run order exposes the leak, forcing an admin bypass or a re-run gamble. This bit PR release: v0.9.3 #1041 (v0.9.3 release PR).Fix
Two clean options — pick whichever fits the file's style:
Option A — per-spy restore (explicit, mirrors the
beforeEachsetup):Option B — blanket restore (also cleans up any other spy installed during a test):
Regression test
Add a smoke test that asserts
os.homedir()returns the process's real home immediately after thelifecycle.test.tssuite completes:Or, less surgical but more general — a
bun:testhook in a top-levelpreloadthat runsmock.restore()at file boundaries.Priority
Low-medium — no production impact, no data risk. Purely a CI/DX problem: it forces PR authors to either re-run the check hoping for a favourable file order or ask for an admin bypass on unrelated PRs. Fix is small and self-contained; regression test would prevent recurrence.