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", () => {
|
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 () => {
|
it("removes stale fusion-ai-merge directories and emits success audits", async () => {
|
||||||
const stale = tempMergeDir();
|
const stale = tempMergeDir();
|
||||||
makeStale(stale);
|
makeStale(stale);
|
||||||
|
|||||||
@@ -14905,7 +14905,19 @@ const movedTask = await this.store.moveTask(task.id, completeLane);
|
|||||||
for (const tempRoot of roots) {
|
for (const tempRoot of roots) {
|
||||||
let entries: string[];
|
let entries: string[];
|
||||||
try {
|
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) {
|
} catch (err: unknown) {
|
||||||
if (!existsSync(tempRoot)) continue;
|
if (!existsSync(tempRoot)) continue;
|
||||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user