From a46f9224c4c223c58c170e070b9b7ea3231a43d8 Mon Sep 17 00:00:00 2001 From: dev-milos <271274416+dev-milos@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:09:40 +0200 Subject: [PATCH] Reset dir singletons at file boundaries in isolate:false projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under isolate:false (v2-core, v2-integration) files in a worker fork share one module graph, so bobbit-dir's project root and the agent-dir runtime state are shared. docker-args.test.ts points the root at memfs fixture paths and never restores it, so a later file that relies on the defaults resolves bobbitStateDir() under the leaked root and fails on a real mkdir under /memfs (mcp-meta-policy: EACCES/ENOENT). The victim moves with worker sharding, which is why it passed on the dev box but failed on CI, and retry cannot mask it — the retry reruns in the same fork. A setup-file beforeAll runs once per worker, not per file, and beforeEach runs after the file's own beforeAll. onCollectStart runs per file before its module loads, so the runner resets the baseline there. env is left alone — the fork-scoped gateway owns BOBBIT_* as intentional fork-wide state. Repro (fails on master, passes here): VITEST_MAX_WORKERS=1 vitest run --project v2-core \ tests2/core/docker-args.test.ts tests2/core/mcp-meta-policy.test.ts --- tests2/harness/file-boundary-runner.ts | 17 +++++++++++++++++ vitest.config.ts | 4 ++++ 2 files changed, 21 insertions(+) create mode 100644 tests2/harness/file-boundary-runner.ts diff --git a/tests2/harness/file-boundary-runner.ts b/tests2/harness/file-boundary-runner.ts new file mode 100644 index 000000000..ecc1dc5ef --- /dev/null +++ b/tests2/harness/file-boundary-runner.ts @@ -0,0 +1,17 @@ +import { VitestTestRunner } from "vitest/runners"; +import type { File } from "@vitest/runner"; +import { setProjectRoot } from "../../src/server/bobbit-dir.js"; +import { resetAgentDirStateForTests } from "../../src/server/agent-dir-config.js"; + +const BASELINE_CWD = process.cwd(); + +// isolate:false files share a fork's module graph, so the project-root and +// agent-dir singletons leak between files. onCollectStart is the only hook that +// runs per file before its module loads; env is left to the fork-scoped gateway. +export default class FileBoundaryRunner extends VitestTestRunner { + onCollectStart(file: File): void { + setProjectRoot(BASELINE_CWD); + resetAgentDirStateForTests(); + return super.onCollectStart(file); + } +} diff --git a/vitest.config.ts b/vitest.config.ts index 374cfe7d4..9e1f3e4bb 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -50,6 +50,8 @@ const shared = { }, }; const tier1SetupFiles = ["tests2/harness/tier1-spawn-guard.ts"]; +// Per-file reset of leaking dir singletons for the isolate:false projects. +const fileBoundaryRunner = "tests2/harness/file-boundary-runner.ts"; const coverage = { provider: "v8" as const, @@ -99,6 +101,7 @@ export default defineConfig({ ...shared, name: "v2-core", environment: "node", + runner: fileBoundaryRunner, setupFiles: tier1SetupFiles, include: execution.core, }, @@ -123,6 +126,7 @@ export default defineConfig({ ...shared, name: "v2-integration", environment: "node", + runner: fileBoundaryRunner, setupFiles: tier1SetupFiles, include: execution.integration, testTimeout: 60_000,