From 245e1280edc909a7cbbb5607f8734bd11359ff5b Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 11 Jun 2026 12:22:02 -0700 Subject: [PATCH] Fix agents --- AGENTS.md | 6 +++ .../core/src/__test-utils__/vitest-setup.ts | 53 +++++++++++++++---- scripts/lib/test-quarantine.json | 4 ++ 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0eddb8541f..0391010eab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,6 +112,12 @@ pnpm verify:workspace # deep opt-in verification (lint -> test:full -> build); Never kill processes on port 4040 and never start test servers on 4040. Use `--port 0` or another free port. +### Never run an unbounded `find` against the system temp directory + +Do not issue a recursive `find` (or any unbounded recursive directory walk) rooted at the OS temp directory — `$TMPDIR`, `/tmp`, or macOS `/var/folders/...` (canonical `/private/var/...`). The temp root can hold an enormous number of entries on CI and long-lived dev hosts, so a broad scan can hang for minutes and pin I/O. + +When you need a Fusion temp artifact, target the known prefix directly and list a single level with a prefix filter — never walk the whole temp tree. The canonical bounded pattern is the engine's own sweep: a non-recursive `readdirSync(tmpdir())` filtered by a known prefix such as `fusion-ai-merge-` (`SelfHealingManager.cleanupStaleTempMergeWorktrees()` in `packages/engine/src/self-healing.ts`). Scoped `find` calls under a project worktree or `.fusion/` are fine; only the broad temp-root scan is forbidden. + ### Engine Process Rules #### Never use `execSync` for user-configured commands diff --git a/packages/core/src/__test-utils__/vitest-setup.ts b/packages/core/src/__test-utils__/vitest-setup.ts index 6b0c4de5cc..74681d3ee2 100644 --- a/packages/core/src/__test-utils__/vitest-setup.ts +++ b/packages/core/src/__test-utils__/vitest-setup.ts @@ -45,6 +45,7 @@ const { mkdtempSync, mkdirSync, readFileSync, + readdirSync, rmSync, realpathSync, existsSync, @@ -192,20 +193,32 @@ function isProcessAlive(pid: number): boolean { } } +function removeTmpdirRedirectSinkForPid(ownerPid: number): void { + try { + rmSync(join(WORKER_ROOT, `redir-${ownerPid}`), { recursive: true, force: true }); + } catch { + // Ignore stale-sink cleanup failures; global teardown still owns WORKER_ROOT. + } +} + function sweepDeadTmpdirRedirectSinks(): void { if (tmpdirRedirectSweepComplete) return; tmpdirRedirectSweepComplete = true; - let ownerPids: number[]; + // Registry-backed cleanup avoids scanning the OS temp root while still + // reclaiming redirect sinks from fork-pool workers that were hard-killed. + let ownerPids: number[] = []; try { ownerPids = Array.from(new Set( readFileSync(TMPDIR_REDIRECT_REGISTRY, "utf8") - .split(/\r?\n/) + .split(/ ? +/) .map((line) => Number.parseInt(line, 10)) .filter((pid) => Number.isInteger(pid) && pid > 0), )); } catch { - return; + // The registry may not exist yet. The bounded WORKER_ROOT sweep below still + // catches legacy redirect dirs created before the registry was introduced. } const liveOwnerPids: number[] = []; @@ -215,15 +228,30 @@ function sweepDeadTmpdirRedirectSinks(): void { continue; } - try { - rmSync(join(WORKER_ROOT, `redir-${ownerPid}`), { recursive: true, force: true }); - } catch { - // Ignore stale-sink cleanup failures; global teardown still owns WORKER_ROOT. + removeTmpdirRedirectSinkForPid(ownerPid); + } + + // Preserve the local self-healing behavior for redirect dirs that predate the + // registry or whose registry append was skipped. This is a single-level scan + // of WORKER_ROOT (not the OS temp root) and only touches dead pid-owned dirs. + try { + for (const entry of readdirSync(WORKER_ROOT)) { + const match = /^redir-(\d+)$/.exec(entry); + if (!match) continue; + const ownerPid = Number.parseInt(match[1], 10); + if (ownerPid === process.pid || liveOwnerPids.includes(ownerPid) || isProcessAlive(ownerPid)) { + continue; + } + removeTmpdirRedirectSinkForPid(ownerPid); } + } catch { + // Best-effort only; stale entries are harmless and swept by future workers. } try { - writeFileSync(TMPDIR_REDIRECT_REGISTRY, liveOwnerPids.length > 0 ? `${liveOwnerPids.join("\n")}\n` : ""); + writeFileSync(TMPDIR_REDIRECT_REGISTRY, liveOwnerPids.length > 0 ? `${liveOwnerPids.join(" +")} +` : ""); } catch { // Best-effort only; stale entries are harmless and swept by future workers. } @@ -236,7 +264,8 @@ function ensureTmpdirRedirectSink(): string { const sink = join(WORKER_ROOT, `redir-${process.pid}`); mkdirSync(sink, { recursive: true }); try { - appendFileSync(TMPDIR_REDIRECT_REGISTRY, `${process.pid}\n`); + appendFileSync(TMPDIR_REDIRECT_REGISTRY, `${process.pid} +`); } catch { // Best-effort only; the process exit hook and global teardown still clean up. } @@ -256,6 +285,11 @@ function ensureTmpdirRedirectSink(): string { return sink; } +/** + * If a mkdtemp prefix points straight at the OS temp root, rewrite it into a + * swept per-process sink under WORKER_ROOT. Prefixes already nested under a + * subdirectory pass through unchanged, as do non-string prefixes (Buffer/URL). + */ function redirectTmpdirPrefix(prefix: T): T { if (typeof prefix !== "string") return prefix; @@ -264,6 +298,7 @@ function redirectTmpdirPrefix(prefix: T): T { return join(ensureTmpdirRedirectSink(), basename(prefix)) as T; } +} function ensureIsolatedHome(): void { const existingHome = process.env.HOME ?? process.env.USERPROFILE; diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json index c8f1c06d6d..756921e1c0 100644 --- a/scripts/lib/test-quarantine.json +++ b/scripts/lib/test-quarantine.json @@ -11,6 +11,10 @@ "reason": "Flake: vi.mock('node:child_process') occasionally doesn't take under workspace-concurrent runs, letting real git binary leak and report staged files unrelated to test scope (trips FileScopeViolationError). Same logic covered by real-git fixture tests in reliability-interactions/workflow-and-file-scope. FN-6206.", "quarantinedAt": "2026-06-10" }, +<<<<<<< Updated upstream +======= + +>>>>>>> Stashed changes { "file": "packages/engine/src/__tests__/merger-ai-cleanup.test.ts", "reason": "Flake observed during FN-6206 verification: `pruneExistingAiMergeWorktrees skips active-session paths` failed in full `pnpm --filter @fusion/engine test` runs while the file passed standalone, indicating suite-order/concurrency sensitivity. Follow-up FN-6207.",