From 3e33d7f8cf768a52e1320ab00823533dbac3ed74 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 08:01:29 +0000 Subject: [PATCH 1/3] test: restore all mocks in storage suite afterEach to stop spy cascades A test in this file that fails before its inline mockRestore() leaks its fs spy into every later test: a later vi.spyOn on the same method returns the SAME leaked spy, so a passthrough binding captured from it recurses into the new test's own mock (a Maximum call stack crash observed in sandboxed environments where the EACCES baseline tests die mid-run). The top-level afterEach restored env vars but never mocks; vi.restoreAllMocks() there guarantees one failure cannot cascade. No assertion changes; the per-test mockRestore() calls remain as harmless redundancy. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --- test/storage.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/storage.test.ts b/test/storage.test.ts index 9778292e..6e97eff4 100644 --- a/test/storage.test.ts +++ b/test/storage.test.ts @@ -53,6 +53,11 @@ describe("storage", () => { if (_origCODEX_MULTI_AUTH_DIR !== undefined) process.env.CODEX_MULTI_AUTH_DIR = _origCODEX_MULTI_AUTH_DIR; else delete process.env.CODEX_MULTI_AUTH_DIR; + // A test that fails before its inline mockRestore() leaks its fs spy into + // every later test (a later vi.spyOn returns the SAME leaked spy, so a + // passthrough binding recurses into the new test's own mock). Restore all + // spies unconditionally so one failure cannot cascade. + vi.restoreAllMocks(); }); describe("account storage directory hardening", () => { From 7204596d0fd30b8abd1dfd00a70315b7af1e0475 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 08:05:50 +0000 Subject: [PATCH 2/3] test: restore all mocks after every test suite-wide Extends the storage-suite fix to the whole runner: a scan found 16 more test files with the same vulnerability (bare mockRestore() at the end of a test body, no global restore hook), where one mid-test failure leaks a spy that a later vi.spyOn on the same method returns verbatim, sending passthrough bindings into infinite recursion. No suite creates spies in beforeAll, so an after-each restoreAllMocks in the shared global-sandbox setup file is safe and contains the cascade everywhere. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --- test/helpers/global-sandbox.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/helpers/global-sandbox.ts b/test/helpers/global-sandbox.ts index b763ba48..c0077cc3 100644 --- a/test/helpers/global-sandbox.ts +++ b/test/helpers/global-sandbox.ts @@ -28,3 +28,14 @@ process.env.CODEX_MULTI_AUTH_DIR = join(SANDBOX_ROOT, ".codex", "multi-auth"); // Expose for assertions / debugging. (globalThis as { __CMA_TEST_SANDBOX_ROOT__?: string }).__CMA_TEST_SANDBOX_ROOT__ = SANDBOX_ROOT; + +// Suite-wide spy hygiene (see PR #590): a test that fails before its inline +// mockRestore() leaks its spy, and a later vi.spyOn on the same method returns +// the SAME leaked spy — so passthrough bindings captured from it recurse into +// the new test's own mock. Restoring after every test contains the cascade. +// No suite creates spies in beforeAll, so an after-each restore is safe. +import { afterEach, vi } from "vitest"; + +afterEach(() => { + vi.restoreAllMocks(); +}); From 72e30edce22dbd311c2f8a0ef0b7bce26ecd4085 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 08:11:50 +0000 Subject: [PATCH 3/3] test: note why the storage suite keeps its local restoreAllMocks https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB --- test/storage.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/storage.test.ts b/test/storage.test.ts index 6e97eff4..894c3040 100644 --- a/test/storage.test.ts +++ b/test/storage.test.ts @@ -56,7 +56,10 @@ describe("storage", () => { // A test that fails before its inline mockRestore() leaks its fs spy into // every later test (a later vi.spyOn returns the SAME leaked spy, so a // passthrough binding recurses into the new test's own mock). Restore all - // spies unconditionally so one failure cannot cascade. + // spies unconditionally so one failure cannot cascade. The global + // afterEach in test/helpers/global-sandbox.ts now does this for every + // suite; this local call stays as defense-in-depth for the suite that + // actually exhibited the cascade, in case the global hook ever moves. vi.restoreAllMocks(); });