fix(engine): reap leaked fn-verify verification worktrees in the temp-dir sweep
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/fn-verify-worktree-reaping.md
Normal file
7
.changeset/fn-verify-worktree-reaping.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Leaked verification worktrees are now reaped, so planning no longer queues behind exhausted slots.
|
||||
category: fix
|
||||
dev: mission-verification's fn-verify-* checkouts leaked on process death (dispose is in-process best-effort); the self-healing temp-dir sweep now includes the fn-verify- prefix under tmpdir() with the same age gates and active-session refusal.
|
||||
@@ -193,6 +193,28 @@ describe("SelfHealingManager worktrees-dir sweeps", () => {
|
||||
});
|
||||
|
||||
describe("SelfHealingManager temp-dir AI merge worktree sweep", () => {
|
||||
it("removes stale fn-verify verification checkouts from tmpdir (leak found on the live board)", async () => {
|
||||
/*
|
||||
FNXC:TempWorktreeSweep 2026-07-31-22:55:
|
||||
GitCheckoutMaterializer's dispose() is in-process best-effort; a killed process leaks the
|
||||
fn-verify-* checkout AND its git worktree registration forever, because no sweep knew the
|
||||
prefix. Reverting the sweep-prefix change makes this test fail (the dir survives).
|
||||
*/
|
||||
const stale = tempMergeDir(`fn-verify-${Math.random().toString(36).slice(2)}`);
|
||||
makeStale(stale);
|
||||
const staleApp = tempMergeDir(`fn-verify-app-${Math.random().toString(36).slice(2)}`);
|
||||
makeStale(staleApp);
|
||||
const fresh = tempMergeDir(`fn-verify-fresh-${Math.random().toString(36).slice(2)}`);
|
||||
const { manager } = makeManager();
|
||||
|
||||
await expect(sweep(manager)).resolves.toBe(2);
|
||||
|
||||
expect(existsSync(stale)).toBe(false);
|
||||
expect(existsSync(staleApp)).toBe(false);
|
||||
// Young checkouts may belong to a live verification run — the age gate must hold.
|
||||
expect(existsSync(fresh)).toBe(true);
|
||||
});
|
||||
|
||||
it("removes stale fusion-ai-merge directories and emits success audits", async () => {
|
||||
const stale = tempMergeDir();
|
||||
makeStale(stale);
|
||||
|
||||
@@ -14905,7 +14905,19 @@ const movedTask = await this.store.moveTask(task.id, completeLane);
|
||||
for (const tempRoot of roots) {
|
||||
let entries: string[];
|
||||
try {
|
||||
entries = readdirSync(tempRoot).filter((entry) => entry.startsWith("fusion-ai-merge-"));
|
||||
/*
|
||||
FNXC:TempWorktreeSweep 2026-07-31-22:55:
|
||||
`fn-verify-` (and `fn-verify-app-`) checkouts from mission-verification's
|
||||
GitCheckoutMaterializer leak when the process dies between materialize and dispose — seven
|
||||
registered worktrees aged Jul 24-27 were found holding registrations, and on the live board
|
||||
the visible symptom was planning admission queueing behind exhausted worktree slots
|
||||
("queued to plan" for ~6 minutes). Their dispose() is best-effort in-process only; this
|
||||
sweep is the only out-of-process reaper, so it must know the prefix. Verification checkouts
|
||||
are detached throwaways that only ever live under tmpdir(), so the extra prefixes apply to
|
||||
that root alone; the same age gates, active-session refusal, and audit rows govern them.
|
||||
*/
|
||||
const sweepPrefixes = tempRoot === tmpdir() ? ["fusion-ai-merge-", "fn-verify-"] : ["fusion-ai-merge-"];
|
||||
entries = readdirSync(tempRoot).filter((entry) => sweepPrefixes.some((prefix) => entry.startsWith(prefix)));
|
||||
} catch (err: unknown) {
|
||||
if (!existsSync(tempRoot)) continue;
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
|
||||
Reference in New Issue
Block a user