Skip to content

fix: real CLI-probe tests leak scratch state into the vitest temp root (#9039) - #9050

Merged
atomantic merged 4 commits into
mainfrom
claim/issue-9039
Sep 28, 2026
Merged

atomantic merged 4 commits into
mainfrom
claim/issue-9039

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

Fixes the leak #9039 tracked, by fixing its actual root causes rather than
just growing the allowlist.

  • server/lib/commandExists.js's shared probe() (used by every --version
    CLI capability check) and server/lib/npmGlobalBin.js's npm prefix -g
    probe now pin TMPDIR/TMP/TEMP to a throwaway per-spawn scratch dir,
    removed once the probe settles. kilo, opencode (both self-extracting
    Node-launcher binaries) and npm itself all write their own scratch/cache
    state (a session dir, Node's own module compile cache) into whatever
    TMPDIR they inherit on every invocation — this keeps that state out of the
    caller's real TMPDIR entirely, in production too, not just under test.
  • server/services/providerRuntimeInstaller.js's runtime-status sweep and
    server/services/codexOssSupport.js's --oss probe now skip the real PATH
    scan/spawn under the test runner unless a caller explicitly injects its own
    findCommand/probeCommand/run — so an incidental route/service test
    that never asked to probe a real CLI stops shelling out to whatever happens
    to be on the developer's own PATH. Every genuinely intentional
    real-CLI-probe test already injects its own deps (confirmed by inspection —
    none of providerRuntimeInstaller.test.js's or codexOssSupport.test.js's
    cases rely on the default) and is completely unaffected by this gate.
  • server/services/agentRunReconciler.test.js's path-traversal test writes
    one fixture deliberately outside its RUNS_DIR (that's the point of the
    test) but never swept it in afterAll — not a third-party leak at all,
    just a missed cleanup. Fixed.
  • KNOWN_THIRD_PARTY_CLI_SCRATCH in server/test/runTempRoot.js shrinks from
    four names to one: node-compile-cache still reappears very rarely (once
    in ~2,500 files locally) with no real CLI spawn in the trace that produced
    it, so it stays allowlisted rather than pinned on an unconfirmed cause.
    kilo, opencode, and escape are fully removed — their root causes are
    fixed, not just muted.

Three rounds of local provider:codex review each caught a real issue,
fixed in follow-up commits: a synchronous mkdtempSync throw that could
have broken the "probe never rejects" contract, an asymmetric
skipRealSpawn gate that still let a real spawn through the probeCommand
default, and a skipped probe answer that would have sat in the TTL cache
where a later real-probe call could read it back instead of re-probing.

Test plan

  • cd server && node_modules/.bin/vitest run — full suite, twice, exits 0
    with no ⚠️ test temp leak line for kilo/opencode/escape, on a
    machine with both CLIs installed on PATH (per the issue's acceptance
    criteria).
  • npm run pregate — green.
  • Targeted reruns after each review-fix commit:
    server/lib/commandExists.test.js, server/lib/npmGlobalBin.test.js,
    server/services/providerRuntimeInstaller.test.js,
    server/services/providerPrerequisites.test.js,
    server/services/codexOssSupport.test.js,
    server/services/agentRunReconciler.test.js,
    server/routes/providers.composite.test.js.

Closes #9039

…mp root (#9039)

Real kilo/opencode/npm child processes spawned by provider-readiness probes
wrote their own scratch/cache state (a session dir, Node's module compile
cache) straight into whatever TMPDIR they inherited, which since #9032 is
PortOS's run-scoped vitest temp root. Two fixes:

- commandExists.js's probe() and npmGlobalBin.js's npm-prefix probe now pin
  TMPDIR/TMP/TEMP to a throwaway per-spawn scratch dir, removed once the
  probe settles, so a real binary's own scratch state never reaches the
  caller's TMPDIR at all (production benefit too, not just tests).
- providerRuntimeInstaller.js's runtime status sweep and codexOssSupport.js's
  --oss probe now skip the real PATH scan/spawn under the test runner unless
  a caller explicitly injects its own findCommand/probeCommand/run — so an
  incidental route/service test that never asked to probe a real CLI stops
  shelling out to whatever happens to be on the developer's PATH. Every
  intentional real-CLI-probe test already injects its own deps and is
  unaffected.

Also fixed agentRunReconciler.test.js's own path-traversal fixture, which
wrote one directory outside its RUNS_DIR and never swept it — that was never
a third-party leak, just a missed cleanup.

Shrinks KNOWN_THIRD_PARTY_CLI_SCRATCH in runTempRoot.js to the one residual,
rare entry (node-compile-cache) that no longer correlates with any CLI spawn
in the trace that produced it.
… be created

Local codex review of the #9039 fix caught a real regression: mkdtempSync()
ran synchronously before the promise chain, so a full disk or unwritable
tmpdir would throw out of probe() instead of resolving to null like every
other probe failure — breaking the "commandExists/commandOutput never
reject" contract every caller relies on.
…Command

Local codex review (round 2) of the #9039 fix caught an asymmetry: an
explicit skipRealSpawn: true combined with only a custom findCommand still
fell through to the REAL commandOutput default for probeCommand, so a
caller relying on skipRealSpawn alone to suppress real spawning could still
shell out. Both fallbacks now gate on skip consistently.
Local codex review (round 3) flagged that a skipped test-runner probe wrote
its synthetic "not probed" result into the same TTL cache a real probe uses,
so a later call in the same module instance that injects real
findCommand/probeCommand deps (e.g. a hasCli()-gated integration test
sharing the cache) would read the stale skipped result back instead of
running its own real probe. Matches the "NOT PROBED, deliberately not
cached" contract codexOssSupport.js already uses for the same reason.
@atomantic
atomantic merged commit 0a70271 into main Sep 28, 2026
9 checks passed
@atomantic
atomantic deleted the claim/issue-9039 branch September 28, 2026 07:49
atomantic added a commit that referenced this pull request Sep 28, 2026
node-compile-cache still turned up rarely in the run-scoped vitest
temp root even after #9050 isolated the one confirmed real-CLI spawn
(npmGlobalBin.js's `npm prefix -g`) into its own TMPDIR. The cache is
process-wide (NODE_COMPILE_CACHE env, or a CLI calling
module.enableCompileCache() on itself), not tied to one call site, so
no single spawn helper's isolation could be proven to cover every
child that might inherit the run's TMPDIR.

Set NODE_DISABLE_COMPILE_CACHE=1 tree-wide for the test run instead
(server/vitest.config.js, main process and worker env) - honored
since Node v22.1, within this repo's supported range. Remove the
now-unneeded KNOWN_THIRD_PARTY_CLI_SCRATCH allowlist entry from
server/test/runTempRoot.js so a recurrence fails the run again.
Document the new env var in envExampleDrift.test.js's INHERITED_ENV
allowlist.
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.

Real CLI-probe tests (kilo/opencode) leak scratch state into the vitest temp root

1 participant