FN-5872: clear active-session registry on PR cleanup
Ensure PR-mode merge cleanup removes stale active-session registry state before deleting worktrees. - unregister the task worktree from the active-session registry during merged-task artifact cleanup - export the active-session registry from the engine entrypoint for lifecycle cleanup usage - add CLI and engine regression coverage for registry cleanup and missing-entry best-effort behavior - add a patch changeset for the published CLI package Files changed: .changeset/fn-5872-registry-cleanup.md | 5 ++++ .../src/commands/__tests__/task-lifecycle.test.ts | 29 +++++++++++++++++++++- packages/cli/src/commands/task-lifecycle.ts | 8 +++++- .../pr-mode-worktree-invariants.test.ts | 11 +++++--- packages/engine/src/index.ts | 1 + 5 files changed, 49 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-5872 Fusion-Task-Lineage: d4db24a3-7ae0-4d07-b029-7f30b2f65d2e
This commit is contained in:
5
.changeset/fn-5872-registry-cleanup.md
Normal file
5
.changeset/fn-5872-registry-cleanup.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Clear stale active-session registry entries when PR-mode merge cleanup removes a task worktree.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
// Mock child_process so we can intercept the `git push -u origin <branch>`
|
||||
@@ -23,6 +23,7 @@ vi.mock("node:child_process", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
import { activeSessionRegistry } from "@fusion/engine";
|
||||
import {
|
||||
cleanupMergedTaskArtifacts,
|
||||
processPullRequestMergeTask,
|
||||
@@ -1252,6 +1253,11 @@ describe("cleanupMergedTaskArtifacts FN-5455", () => {
|
||||
beforeEach(() => {
|
||||
execMock.mockReset();
|
||||
execMock.mockReturnValue("");
|
||||
activeSessionRegistry.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
activeSessionRegistry.clear();
|
||||
});
|
||||
|
||||
it("FN-5455: releases pool lease before removing worktree and deleting branch", async () => {
|
||||
@@ -1284,4 +1290,25 @@ describe("cleanupMergedTaskArtifacts FN-5455", () => {
|
||||
expect(execMock).toHaveBeenCalledWith(expect.stringContaining('git worktree remove "/repo/wt-d" --force'), expect.any(Object));
|
||||
expect(execMock).toHaveBeenCalledWith(expect.stringContaining('git branch -d "fusion/fn-5455-d"'), expect.any(Object));
|
||||
});
|
||||
|
||||
it("FN-5872: cleanup clears active-session registry entry", async () => {
|
||||
const worktree = "/repo/wt-fn-5872";
|
||||
activeSessionRegistry.registerPath(worktree, {
|
||||
taskId: "FN-5872-A",
|
||||
kind: "executor",
|
||||
ownerKey: "FN-5872-A",
|
||||
});
|
||||
|
||||
expect(activeSessionRegistry.lookupByPath(worktree)).not.toBeNull();
|
||||
|
||||
await cleanupMergedTaskArtifacts("/repo", { id: "FN-5872-A", worktree } as never);
|
||||
|
||||
expect(activeSessionRegistry.lookupByPath(worktree)).toBeNull();
|
||||
});
|
||||
|
||||
it("FN-5872: cleanup remains a no-throw best-effort when no registry entry exists", async () => {
|
||||
await expect(
|
||||
cleanupMergedTaskArtifacts("/repo", { id: "FN-5872-B", worktree: "/repo/wt-fn-5872-missing" } as never),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ const execAsync = promisify(exec);
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
import { resolveTaskMergeTarget } from "@fusion/core";
|
||||
import type { Settings, TaskDetail, PrInfo, MergeResult, BranchGroup, BranchGroupPrState, Task } from "@fusion/core";
|
||||
import { resolveIntegrationBranch } from "@fusion/engine";
|
||||
import { activeSessionRegistry, resolveIntegrationBranch } from "@fusion/engine";
|
||||
import type { WorktreePool } from "@fusion/engine";
|
||||
|
||||
/**
|
||||
@@ -196,6 +196,12 @@ export async function cleanupMergedTaskArtifacts(
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
activeSessionRegistry.unregisterPath(task.worktree);
|
||||
} catch {
|
||||
// Best-effort cleanup — registry entry may already be absent or registry state divergent.
|
||||
}
|
||||
|
||||
try {
|
||||
await execAsync(`git worktree remove "${task.worktree}" --force`, {
|
||||
cwd,
|
||||
|
||||
@@ -283,10 +283,15 @@ describe("FN-5420 reliability interactions: PR mode worktree invariants", () =>
|
||||
expect(releaseSpy).toHaveBeenCalledWith(path, "FN-5455-THROW");
|
||||
});
|
||||
|
||||
it.skip("FN-5456 follow-up required: cleanup should clear active-session registry entry", async () => {
|
||||
const path = "/tmp/fn-5456-session";
|
||||
activeSessionRegistry.registerPath(path, { taskId: "FN-5456", kind: "executor", ownerKey: "FN-5456" });
|
||||
it("FN-5872: cleanup clears active-session registry entry", async () => {
|
||||
const path = "/tmp/fn-5872-session";
|
||||
activeSessionRegistry.registerPath(path, { taskId: "FN-5872", kind: "executor", ownerKey: "FN-5872" });
|
||||
expect(activeSessionRegistry.lookupByPath(path)).not.toBeNull();
|
||||
|
||||
const { cleanupMergedTaskArtifacts } = await loadPrLifecycleModule();
|
||||
await cleanupMergedTaskArtifacts("/tmp", { id: "FN-5872", worktree: path } as any);
|
||||
|
||||
expect(activeSessionRegistry.lookupByPath(path)).toBeNull();
|
||||
});
|
||||
|
||||
it("FN-5420/FN-5279: mergeIntegrationWorktree setting does not gate PR-mode processing", async () => {
|
||||
|
||||
@@ -172,6 +172,7 @@ export {
|
||||
type MockScript,
|
||||
type MockScriptContext,
|
||||
} from "./providers/index.js";
|
||||
export { activeSessionRegistry } from "./active-session-registry.js";
|
||||
export { WorktreePool, scanIdleWorktrees, cleanupOrphanedWorktrees, reapOrphanWorktrees } from "./worktree-pool.js";
|
||||
export {
|
||||
pruneWorktreeAdminEntries,
|
||||
|
||||
Reference in New Issue
Block a user