diff --git a/apps/server/test/db/agent-manager.test.ts b/apps/server/test/db/agent-manager.test.ts index 218062bb8..43c169266 100644 --- a/apps/server/test/db/agent-manager.test.ts +++ b/apps/server/test/db/agent-manager.test.ts @@ -2431,38 +2431,23 @@ describe("AgentManager", () => { }); it("should skip session ownership logic for non-claude agents", async () => { - // Point the codex harvest at an empty temp dir: without this the - // harvester walks the real ~/.codex/sessions, and on a developer - // machine (or the self-hosted runner) that can be gigabytes of - // rollout files — enough to blow the test timeout. - const codexHome = await mkdtemp( - path.join(os.tmpdir(), "dispatch-codex-home-") - ); - const previousCodexHome = process.env.CODEX_HOME; - process.env.CODEX_HOME = codexHome; - try { - const agent = await manager.createAgent({ - name: "codex-agent", - type: "codex", - cwd: "/tmp", - useWorktree: false, - }); + const agent = await manager.createAgent({ + name: "codex-agent", + type: "codex", + cwd: "/tmp", + useWorktree: false, + }); - // Should not throw — codex agents don't use session ownership - await manager.harvestAgentTokens(agent); + // Should not throw — codex agents don't use session ownership + await manager.harvestAgentTokens(agent); - // No sessions exist under the isolated CODEX_HOME, so nothing is - // harvested. - const usage = await pool.query( - `SELECT COUNT(*)::int AS count FROM agent_token_usage WHERE agent_id = $1`, - [agent.id] - ); - expect(usage.rows[0].count).toBe(0); - } finally { - if (previousCodexHome === undefined) delete process.env.CODEX_HOME; - else process.env.CODEX_HOME = previousCodexHome; - await rm(codexHome, { recursive: true, force: true }); - } + // vitest.config.ts points CODEX_HOME at an empty directory for the whole + // suite, so no rollout files exist and nothing is harvested. + const usage = await pool.query( + `SELECT COUNT(*)::int AS count FROM agent_token_usage WHERE agent_id = $1`, + [agent.id] + ); + expect(usage.rows[0].count).toBe(0); }); }); diff --git a/apps/server/vitest.config.ts b/apps/server/vitest.config.ts index 802baafae..88f6b6fd9 100644 --- a/apps/server/vitest.config.ts +++ b/apps/server/vitest.config.ts @@ -1,10 +1,37 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; import { defineConfig } from "vitest/config"; +// The Codex token harvester resolves its rollout directory from `CODEX_HOME`, +// falling back to the host's real `~/.codex` (see src/agents/codex-sessions.ts). +// Any test that harvests a codex agent — including the fire-and-forget harvest +// `stopAgent` kicks off, which no per-test override can wrap — then recursively +// walks that directory and reads the head of every rollout file. On a machine +// that actually uses Codex that is gigabytes of I/O per run, and the self-hosted +// CI runner is such a machine. Point the whole suite at an empty directory so no +// test ever reads the host's CLI history. +// +// `mkdtempSync` rather than a fixed name: it creates a fresh 0700 directory with +// a random suffix, so it can never reuse a stale tree, adopt a planted +// `sessions` directory, or follow a pre-existing symlink the way +// `mkdirSync(fixedName, { recursive: true })` silently would. Removed on exit; +// concurrent runs each get their own. +const codexHome = mkdtempSync( + path.join(os.tmpdir(), "dispatch-server-vitest-codex-") +); +process.on("exit", () => { + rmSync(codexHome, { recursive: true, force: true }); +}); + export default defineConfig({ test: { globals: true, testTimeout: 30_000, hookTimeout: 30_000, + env: { + CODEX_HOME: codexHome, + }, include: ["test/**/*.test.ts"], exclude: [ "**/node_modules/**",