Skip to content

test(server): isolate the vitest suite from the host's ~/.codex - #1053

Merged
selfcontained merged 2 commits into
mainfrom
debt/server-test-codex-home-isolation
Sep 4, 2026
Merged

test(server): isolate the vitest suite from the host's ~/.codex#1053
selfcontained merged 2 commits into
mainfrom
debt/server-test-codex-home-isolation

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

Two files. apps/server/vitest.config.ts gains a test.env block pointing CODEX_HOME at an empty directory under os.tmpdir() for the whole server suite; apps/server/test/db/agent-manager.test.ts drops the per-test CODEX_HOME swap that is now redundant.

Why this is tech debt

codexSessionsDir() (apps/server/src/agents/codex-sessions.ts:11) resolves CODEX_HOME || ~/.codex, and discoverCodexRolloutFiles then recursively walks that tree reading the first 20 lines of every rollout .jsonl looking for the [dispatch:<agentId>] tag. So any server test that harvests a codex agent reads the developer's real Codex history. On this machine that is 5.7 GB / ~11,700 files. The self-hosted CI runner is the same machine (studio.local), so CI reads the same directory — this was never a "works on CI" situation.

AgentManager.harvestAgentTokens already opens with if (!this.runtime.tracksSessions()) return; and the comment there says the guard "keeps inert dev/test servers from scanning session history that belongs to the host environment". The unit suite constructs a manager whose runtime does track sessions, so it bypasses that guard — the intent was already recorded in the code, just not enforced for tests.

PR #1041 patched the single test whose 30s budget it blew by swapping CODEX_HOME inside that one test. That fixed the named symptom but not the class: stopAgent harvests fire-and-forget (apps/server/src/agents/manager.ts:1173), so every codex agent stopped by another test in the file still walked the real directory in the background, and no per-test override can wrap an un-awaited call. The file was still burning ~24s on host I/O after #1041.

Proof (the wall clock is the assertion — no new test)

apps/server/test/db/agent-manager.test.ts, same DB, same machine:

config result
before this PR (as on main) 31.4s, 129 passed
this PR 7.5s, 129 passed
this PR's test simplification without the config change 55.5s, the should skip session ownership logic for non-claude agents test times out at 30000ms

That third row is the non-vacuity check: I reverted only vitest.config.ts and re-ran, and the exact test named in the flake report failed. Restored, and it passes in 7.5s.

Full server suite with the change: 3130 passed / 9 skipped, 40.4s. pnpm run check clean. E2E: 195 passed / 13 skipped.

Explicitly NOT changed

  • The Claude-side harvester. claudeProjectRoot() (token-harvester.ts:60) is hardcoded to os.homedir()/.claude/projects with no env knob, so isolating it would mean either overriding HOME for the whole suite (huge blast radius — git, docker, pg all read it) or adding a new env knob to production code, which is a behavior change rather than debt cleanup. It is also bounded: it readdirs one encoded directory for the agent's cwd rather than walking a tree, and on this machine ~/.claude/projects/-tmp does not exist, so it is a no-op today. Recorded on the backlog instead.
  • apps/server/test/codex-sessions.test.ts. It sets its own CODEX_HOME per test (including one case that deliberately deletes it to assert the ~/.codex fallback). Those are fixtures under test, not host leakage, and they still pass unchanged — the config value is a default that a test is free to override.
  • apps/server/test/token-harvester.test.ts:431, same reasoning: it points CODEX_HOME at its own fixture tree.
  • The 30s testTimeout. Deliberately untouched — raising it would have hidden a suite doing gigabytes of host I/O on every run.

The one arguable call

Removing #1041's per-test CODEX_HOME wrapper rather than leaving it as belt-and-braces. I removed it because its comment ("without this the harvester walks the real ~/.codex") would become false, and a stale comment asserting a defense that has moved elsewhere is the debt this job exists to remove. See the reviewer question below.

Next run

Backlog: the publishUiEvent: (event: unknown) => void looseness across eight route dep types, together with the agent.upsert/snapshot hasStream wire understatement — they are the same job.

🤖 Generated with Claude Code

selfcontained and others added 2 commits September 4, 2026 03:08
The Codex token harvester resolves its rollout directory from CODEX_HOME and
falls back to the host's real ~/.codex (src/agents/codex-sessions.ts:11). Any
server test that harvests a codex agent then recursively walks that tree and
reads the head of every rollout file. On this machine that is 5.7 GB / ~11.7k
files, and the self-hosted CI runner is the same machine — so it is not a
"works on CI" situation.

PR #1041 patched the one test whose 30s budget it blew by swapping CODEX_HOME
inside that test, but stopAgent harvests fire-and-forget (manager.ts:1173), so
codex agents stopped by other tests still walked the real directory. The file
was still spending ~24s on host I/O.

Point CODEX_HOME at an empty directory for the whole server suite via
vitest.config.ts `test.env`, and drop the now-redundant per-test override.

apps/server/test/db/agent-manager.test.ts alone: 55.5s with one 30s timeout
before, 7.5s and 129/129 after. Full server suite: 3130 passed in 40.4s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 1107 item 2612: a fixed name under os.tmpdir() is not guaranteed to be
empty — mkdirSync(..., { recursive: true }) silently reuses an existing
directory or follows an existing symlink, so a stale or planted `sessions` tree
would put host-controlled data back in the harvester's path.

mkdtempSync creates a fresh 0700 directory with a random suffix on every run, so
it can never adopt an existing tree, and concurrent runs no longer share one.
Removed on process exit.

Verified: zero `dispatch-server-vitest-codex-*` directories in TMPDIR before and
after a run; agent-manager.test.ts 129/129 in 8.1s; full server suite 3130
passed in 32.2s; pnpm run check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 623691f into main Sep 4, 2026
1 check passed
@selfcontained
selfcontained deleted the debt/server-test-codex-home-isolation branch September 4, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant