Files
fusion/packages/engine/src/__tests__/worktree-acquisition.test.ts
Phil Larson 8fe69ac5c8 test: align workspace callback fixtures and gate (#3500)
## Summary
- update workspace worktree test stores for callback-based entry
mutations
- preserve validation, existing-entry, and singular-routing behavior in
the fakes
- canonicalize the macOS worktree path fixture before comparing
persisted state
- remove the stale inert-seam exception left after the review-column
callback became fully supplied

## Test plan
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/node-worktree-isolation.test.ts
src/__tests__/workspace-root-worktree-routing.test.ts
src/__tests__/worktree-acquisition.test.ts --silent=passed-only
--reporter=dot`
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/worktree-acquisition-workspace.test.ts
--silent=passed-only --reporter=dot`
- Directly changed engine workspace suite with PostgreSQL-dependent
blocks skipped: 338 passed, 4 skipped
- `pnpm --filter @fusion/engine typecheck`
- `node scripts/check-inert-flag-seams.mjs`
- `node --test scripts/__tests__/check-inert-flag-seams.test.mjs`
- `pnpm lint`
- `pnpm check:changesets`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved recovery of orphaned worktrees by consistently using
canonical paths.
* Strengthened worktree assignment and updates to preserve existing
entries and prevent stale task state.
* Improved synchronization and validation during concurrent workspace
updates.
* Refined self-healing recovery so branch metadata changes only when
necessary.
* Improved handling of stalled tasks, retry exhaustion, and clearing
obsolete worktree or branch information.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-21 19:31:08 -07:00

1384 lines
60 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { execSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, utimesSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { acquireWorktreePathReservation } from "@fusion/core";
import { acquireTaskWorktree, RepoRootWorktreeError, WorktreeBaseRefreshError } from "../worktree/worktree-acquisition.js";
import { classifyTaskWorktree, PoolDoubleLeaseError } from "../worktree/worktree-pool.js";
import * as desktopArtifacts from "../worktree/worktree-desktop-artifacts.js";
import * as branchConflicts from "../execution/branch-conflicts.js";
import { activeSessionRegistry } from "../agents/active-session-registry.js";
import { NativeWorktreeBackend } from "../worktree/worktree-backend.js";
vi.mock("../worktree/worktree-pool.js", async () => {
const actual = await vi.importActual<any>("../worktree/worktree-pool.js");
return {
...actual,
classifyTaskWorktree: vi.fn().mockResolvedValue({ ok: true }),
isInsideWorktreesDir: vi.fn().mockReturnValue(true),
};
});
vi.mock("../execution/branch-conflicts.js", async () => {
const actual = await vi.importActual<any>("../execution/branch-conflicts.js");
return {
...actual,
classifyBootstrapMisbinding: vi.fn().mockResolvedValue({
isBootstrapMisbinding: false,
ownCommitCount: 0,
foreignCommitCount: 0,
nonAttributedCount: 0,
}),
reanchorBranchToBase: vi.fn().mockResolvedValue({ previousTipSha: "abc", newTipSha: "def" }),
};
});
vi.mock("../worktree/worktree-db-hydrate.js", () => ({
hydrateWorktreeDb: vi.fn().mockResolvedValue({ degraded: false, tasksCopied: 1, documentsCopied: 1, artifactsCopied: 0 }),
}));
vi.mock("../worktree/worktree-desktop-artifacts.js", () => ({
removeDesktopBuildArtifacts: vi.fn().mockResolvedValue({ removed: [], skipped: [], failures: [] }),
}));
/*
FNXC:EngineTests 2026-07-21-00:10:
Pool unit tests use temp dirs that are not real git worktrees. installTaskWorktreeIdentityGuard
resolves git paths and throws there, which the pool catch treats as prepare failure and falls
through to fresh. No-op the guard so classification + pool wiring stay under test.
*/
vi.mock("../worktree/worktree-hooks.js", async () => {
const actual = await vi.importActual<any>("../worktree/worktree-hooks.js");
return {
...actual,
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
};
});
const cleanupPaths: string[] = [];
function track(path: string): string {
cleanupPaths.push(path);
return path;
}
function git(cwd: string, command: string): string {
return execSync(command, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
}
function makeRepo(): string {
const rootDir = track(mkdtempSync(join(tmpdir(), "fn-6861-acquisition-root-")));
git(rootDir, "git init -b main");
git(rootDir, 'git config user.email "test@example.com"');
git(rootDir, 'git config user.name "Test User"');
writeFileSync(join(rootDir, "README.md"), "root\n", "utf-8");
git(rootDir, "git add README.md");
git(rootDir, 'git commit -m "init"');
return rootDir;
}
function seedPreservedOrphans(recoveryRoot: string, count: number): string[] {
mkdirSync(recoveryRoot, { recursive: true });
return Array.from({ length: count }, (_, index) => {
const name = `fn-${100 + index}-${randomUUID()}`;
const path = join(recoveryRoot, name);
mkdirSync(path);
writeFileSync(join(path, "artifact"), `${index}\n`, "utf-8");
const timestamp = new Date(Date.now() - (count - index) * 60_000);
utimesSync(path, timestamp, timestamp);
return name;
});
}
afterEach(() => {
vi.restoreAllMocks();
activeSessionRegistry.clear();
for (const path of cleanupPaths.splice(0)) {
rmSync(path, { recursive: true, force: true });
}
});
describe("acquireTaskWorktree", () => {
const task = {
id: "FN-1",
title: "Task",
description: "Desc",
branch: null,
worktree: null,
} as any;
let store: any;
beforeEach(() => {
vi.clearAllMocks();
activeSessionRegistry.clear();
/*
FNXC:EngineTests 2026-07-20-23:55:
clearAllMocks wipes the hoisted classifyTaskWorktree mockResolvedValue({ ok: true }).
Re-arm it every test so pool acquisition does not treat undefined as unusable and fall through to fresh.
*/
vi.mocked(classifyTaskWorktree).mockResolvedValue({ ok: true });
vi.mocked(desktopArtifacts.removeDesktopBuildArtifacts).mockResolvedValue({ removed: [], skipped: [], failures: [] });
store = {
updateTask: vi.fn().mockResolvedValue(undefined),
logEntry: vi.fn().mockResolvedValue(undefined),
};
});
it("reuses existing usable worktree", async () => {
const worktreePath = process.cwd();
const result = await acquireTaskWorktree({
task: { ...task, worktree: worktreePath, branch: "fusion/fn-1" },
rootDir: dirname(worktreePath),
store,
settings: {},
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(result.source).toBe("existing");
expect(result.worktreePath).toBe(worktreePath);
});
// Regression: FN-5475 — when a resumed worktree's branch was created from
// a poisoned local-main tip carrying a sibling task's commits and has zero
// commits of its own, acquireTaskWorktree must re-anchor inline so the
// executor preflight doesn't pause on contamination forever.
it("re-anchors a resumed branch when classified as bootstrap-misbinding", async () => {
const audit = { git: vi.fn().mockResolvedValue(undefined), filesystem: vi.fn() } as any;
vi.mocked(branchConflicts.classifyBootstrapMisbinding).mockResolvedValueOnce({
isBootstrapMisbinding: true,
ownCommitCount: 0,
foreignCommitCount: 2,
nonAttributedCount: 0,
});
const worktreePath = process.cwd();
const result = await acquireTaskWorktree({
task: { ...task, worktree: worktreePath, branch: "fusion/fn-1" },
rootDir: dirname(worktreePath),
store,
settings: {},
audit,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(result.source).toBe("existing");
expect(vi.mocked(branchConflicts.reanchorBranchToBase)).toHaveBeenCalledTimes(1);
expect(audit.git).toHaveBeenCalledWith(expect.objectContaining({
type: "branch:reanchor",
metadata: expect.objectContaining({ trigger: "resume-misbinding" }),
}));
});
it("does not re-anchor a resumed branch when not misbound", async () => {
const worktreePath = process.cwd();
const result = await acquireTaskWorktree({
task: { ...task, worktree: worktreePath, branch: "fusion/fn-1" },
rootDir: dirname(worktreePath),
store,
settings: {},
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(result.source).toBe("existing");
expect(vi.mocked(branchConflicts.reanchorBranchToBase)).not.toHaveBeenCalled();
});
it("derives distinct per-task working branches for shared branch-group members", async () => {
const createWorktree = vi.fn(async (branchName: string, worktreePath: string) => ({ path: worktreePath, branch: branchName }));
const sharedBranch = "clionboarding";
const sharedContext = { assignmentMode: "shared", groupId: "BG-1", source: "planning" } as const;
const [first, second] = await Promise.all([
acquireTaskWorktree({
task: { ...task, id: "FN-100", worktree: null, branch: sharedBranch, branchContext: sharedContext },
rootDir: process.cwd(),
store,
settings: {},
createWorktree,
}),
acquireTaskWorktree({
task: { ...task, id: "FN-101", worktree: null, branch: sharedBranch, branchContext: sharedContext },
rootDir: process.cwd(),
store,
settings: {},
createWorktree,
}),
]);
expect(first.branch).toBe("fusion/fn-100");
expect(second.branch).toBe("fusion/fn-101");
expect(first.branch).not.toBe(second.branch);
expect(createWorktree).toHaveBeenCalledWith("fusion/fn-100", expect.any(String), "FN-100", "main", false);
expect(createWorktree).toHaveBeenCalledWith("fusion/fn-101", expect.any(String), "FN-101", "main", false);
});
it("keeps per-task-derived and ungrouped branch derivation unchanged", async () => {
const createWorktree = vi.fn(async (branchName: string, worktreePath: string) => ({ path: worktreePath, branch: branchName }));
const perTaskDerived = await acquireTaskWorktree({
task: { ...task, id: "FN-102", worktree: null, branch: "fusion/custom-derived", branchContext: { assignmentMode: "per-task-derived", groupId: "BG-1", source: "planning" } },
rootDir: process.cwd(),
store,
settings: {},
createWorktree,
});
const ungrouped = await acquireTaskWorktree({
task: { ...task, id: "FN-103", worktree: null, branch: null },
rootDir: process.cwd(),
store,
settings: {},
createWorktree,
});
expect(perTaskDerived.branch).toBe("fusion/custom-derived");
expect(ungrouped.branch).toBe("fusion/fn-103");
});
it("adopts a registered pre-fix task checkout before attempting fresh creation", async () => {
const rootDir = makeRepo();
const orphanedPath = join(rootDir, ".worktrees", "pre-fix-fn-1");
mkdirSync(dirname(orphanedPath), { recursive: true });
git(rootDir, `git worktree add -b fusion/fn-1 ${JSON.stringify(orphanedPath)} main`);
writeFileSync(join(orphanedPath, "preserved.txt"), "keep this checkout\n", "utf-8");
git(orphanedPath, "git add preserved.txt");
git(orphanedPath, 'git commit -m "FN-1: preserved pre-fix work"');
const canonicalOrphanedPath = realpathSync(orphanedPath);
const preservedTip = git(orphanedPath, "git rev-parse HEAD");
const createWorktree = vi.fn();
const result = await acquireTaskWorktree({
task: { ...task, worktree: null, branch: null },
rootDir,
store,
settings: {},
createWorktree,
});
expect(result).toMatchObject({ worktreePath: canonicalOrphanedPath, branch: "fusion/fn-1", source: "existing" });
expect(git(orphanedPath, "git rev-parse HEAD")).toBe(preservedTip);
expect(createWorktree).not.toHaveBeenCalled();
expect(store.updateTask).toHaveBeenCalledWith("FN-1", {
worktree: canonicalOrphanedPath,
branch: "fusion/fn-1",
branchWriteOrigin: "engine",
});
});
it("persists a fresh canonical assignment through strict provenance validation", async () => {
const rootDir = makeRepo();
const strictStore = {
...store,
updateTask: vi.fn(async (_id: string, patch: Record<string, unknown>) => {
if (patch.branch !== undefined && patch.branchWriteOrigin === undefined) {
throw new Error("branchWriteOrigin is required when branch is provided");
}
}),
};
const result = await acquireTaskWorktree({
task: { ...task, worktree: null, branch: null },
rootDir,
store: strictStore,
settings: {},
});
expect(result.source).toBe("fresh");
expect(strictStore.updateTask).toHaveBeenCalledWith("FN-1", expect.objectContaining({
worktree: result.worktreePath,
branch: "fusion/fn-1",
branchWriteOrigin: "engine",
}));
});
it("compensates a post-create assignment failure without deleting the task branch", async () => {
const rootDir = makeRepo();
let createdPath: string | undefined;
const rejectingStore = {
...store,
updateTask: vi.fn().mockRejectedValue(new Error("simulated assignment persistence failure")),
};
const createWorktree = vi.fn(async (branchName: string, path: string, _taskId: string, startPoint?: string) => {
createdPath = path;
git(rootDir, `git worktree add -b ${branchName} ${JSON.stringify(path)} ${startPoint ?? "main"}`);
return { path, branch: branchName };
});
await expect(acquireTaskWorktree({
task: { ...task, worktree: null, branch: null },
rootDir,
store: rejectingStore,
settings: {},
createWorktree,
})).rejects.toThrow("simulated assignment persistence failure");
expect(createdPath).toBeDefined();
expect(existsSync(createdPath!)).toBe(false);
expect(git(rootDir, "git show-ref --verify --quiet refs/heads/fusion/fn-1; echo $?")) .toBe("0");
});
it("creates fresh worktrees from the integration branch instead of ambient root HEAD", async () => {
const rootDir = makeRepo();
writeFileSync(join(rootDir, "foreign.txt"), "foreign\n", "utf-8");
git(rootDir, "git checkout -b fusion/fn-foreign");
git(rootDir, "git add foreign.txt");
git(rootDir, 'git commit -m "FN-9999: foreign work"');
const foreignHead = git(rootDir, "git rev-parse HEAD");
const mainHead = git(rootDir, "git rev-parse main");
expect(foreignHead).not.toBe(mainHead);
const createWorktree = vi.fn(async (branchName: string, worktreePath: string, _taskId: string, startPoint?: string) => {
git(rootDir, `git worktree add -b ${branchName} ${JSON.stringify(worktreePath)} ${startPoint ?? ""}`);
return { path: worktreePath, branch: branchName };
});
const result = await acquireTaskWorktree({
task: { ...task, id: "FN-200", worktree: null, branch: null },
rootDir,
store,
settings: {},
createWorktree,
});
expect(createWorktree).toHaveBeenCalledWith("fusion/fn-200", expect.any(String), "FN-200", "main", false);
expect(git(result.worktreePath, "git rev-parse HEAD")).toBe(mainHead);
});
it("acquires from pool when enabled", async () => {
const prepareForTask = vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath: "/tmp/pooled", reclaimed: false });
const release = vi.fn();
const result = await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { recycleWorktrees: true } as any,
pool: {
acquire: (_taskId: string) => "/tmp/pooled",
prepareForTask,
release,
} as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(release).not.toHaveBeenCalled();
expect(result.source).toBe("pool");
expect(prepareForTask).toHaveBeenCalledWith(
"/tmp/pooled",
"fusion/fn-1",
"main",
expect.objectContaining({ requestingTaskId: "FN-1" }),
);
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: "/tmp/pooled", branch: "fusion/fn-1", branchWriteOrigin: "engine" });
expect(desktopArtifacts.removeDesktopBuildArtifacts).toHaveBeenCalledWith("/tmp/pooled", undefined);
});
it("releases acquired pooled worktree when prepareForTask returns reclaimed path", async () => {
const release = vi.fn();
await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { recycleWorktrees: true } as any,
pool: {
acquire: (_taskId: string) => "/tmp/pooled",
prepareForTask: vi.fn().mockResolvedValue({
branch: "fusion/fn-1",
worktreePath: "/tmp/live-existing",
reclaimed: true,
existingTipSha: "abc123",
strandedCommitCount: 2,
}),
release,
} as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(release).toHaveBeenCalledWith("/tmp/pooled", "FN-1");
});
it("falls through to fresh creation when pooled worktree is incomplete and emits detection audit", async () => {
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({ ok: false, classification: "incomplete", reason: "missing or invalid .git metadata" });
const createWorktree = vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" });
const auditGit = vi.fn().mockResolvedValue(undefined);
const remove = vi.fn().mockResolvedValue(undefined);
const result = await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { recycleWorktrees: true } as any,
pool: {
acquire: (_taskId: string) => "/tmp/pooled",
prepareForTask: vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath: "/tmp/pooled", reclaimed: false }),
release: vi.fn(),
} as any,
createWorktree,
audit: { git: auditGit } as any,
backend: { kind: "native", create: vi.fn(), remove } as any,
});
expect(result.source).toBe("fresh");
expect(auditGit).toHaveBeenCalledWith(expect.objectContaining({
type: "worktree:incomplete-detected",
metadata: expect.objectContaining({ classification: "incomplete", source: "pool-acquire" }),
}));
expect(store.logEntry).toHaveBeenCalledWith("FN-1", expect.stringContaining("Pool returned incomplete worktree"), undefined, undefined);
expect(store.logEntry).not.toHaveBeenCalledWith("FN-1", expect.stringMatching(/Refusing to start coding agent/), expect.anything(), expect.anything());
});
it("emits resume detection audit and clears session file when assigned worktree is unregistered", async () => {
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({ ok: false, classification: "unregistered", reason: "not registered in git worktree list" });
const createWorktree = vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" });
const auditGit = vi.fn().mockResolvedValue(undefined);
await acquireTaskWorktree({
task: { ...task, worktree: process.cwd(), branch: "fusion/fn-1", sessionFile: "/tmp/session.json" },
rootDir: process.cwd(),
store,
settings: {} as any,
createWorktree,
audit: { git: auditGit } as any,
});
expect(auditGit).toHaveBeenCalledWith(expect.objectContaining({
type: "worktree:incomplete-detected",
metadata: expect.objectContaining({ classification: "unregistered", source: "resume" }),
}));
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
});
it.each([
{ classification: "incomplete" as const, reason: "missing .git metadata" },
{ classification: "unregistered" as const, reason: "not registered in git worktree list" },
])("reclaims a $classification task-pinned directory before recreating the worktree", async ({ classification, reason }) => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
mkdirSync(join(pinnedPath, ".build"), { recursive: true });
mkdirSync(join(pinnedPath, ".swiftpm"), { recursive: true });
writeFileSync(join(pinnedPath, ".build", "cache"), "stale\n", "utf-8");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification,
reason,
});
const result = await acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
});
expect(result).toMatchObject({
worktreePath: pinnedPath,
branch: "fusion/fn-1",
source: "fresh",
isResume: false,
});
expect(existsSync(join(pinnedPath, ".git"))).toBe(true);
expect(git(rootDir, "git worktree list --porcelain")).toContain(pinnedPath);
const recoveryRoot = join(rootDir, ".fusion", "recovery", "worktrees");
const preserved = readdirSync(recoveryRoot);
expect(preserved).toHaveLength(1);
expect(readFileSync(join(recoveryRoot, preserved[0], ".build", "cache"), "utf-8")).toBe("stale\n");
expect(store.logEntry).toHaveBeenCalledWith(
"FN-1",
expect.stringContaining("Preserved orphaned task-pinned directory"),
expect.stringContaining(join(".fusion", "recovery", "worktrees")),
undefined,
);
});
it("preserves an orphan beside an external worktree root when project recovery is cross-device", async () => {
const rootDir = makeRepo();
const externalWorktrees = track(mkdtempSync(join(tmpdir(), "fn-external-worktrees-")));
const pinnedPath = join(externalWorktrees, "fn-1");
mkdirSync(join(pinnedPath, ".build"), { recursive: true });
writeFileSync(join(pinnedPath, ".build", "cache"), "stale\n", "utf-8");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
});
const actualFs = await vi.importActual<typeof import("node:fs/promises")>("node:fs/promises");
const renameWorktreeDirectory = vi.fn()
.mockRejectedValueOnce(Object.assign(new Error("cross-device link"), { code: "EXDEV" }))
.mockImplementationOnce(actualFs.rename);
const result = await acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", worktreesDir: externalWorktrees, recycleWorktrees: false },
renameWorktreeDirectory,
});
const recoveryRoot = join(externalWorktrees, ".fusion-recovery", "worktrees");
const preservedPath = join(recoveryRoot, readdirSync(recoveryRoot)[0]);
expect(result.worktreePath).toBe(pinnedPath);
expect(readFileSync(join(preservedPath, ".build", "cache"), "utf-8")).toBe("stale\n");
expect(renameWorktreeDirectory).toHaveBeenCalledTimes(2);
});
it("retains only the newest ten generated orphan directories in the primary recovery root", async () => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
const recoveryRoot = join(rootDir, ".fusion", "recovery", "worktrees");
const seeded = seedPreservedOrphans(recoveryRoot, 11);
const unknownPath = join(recoveryRoot, "operator-notes");
const symlinkTarget = track(mkdtempSync(join(tmpdir(), "fn-orphan-retention-symlink-target-")));
const generatedSymlink = join(recoveryRoot, `fn-999-${randomUUID()}`);
mkdirSync(pinnedPath, { recursive: true });
mkdirSync(unknownPath);
symlinkSync(symlinkTarget, generatedSymlink);
activeSessionRegistry.registerPath(join(realpathSync(recoveryRoot), seeded[0]), {
taskId: "FN-RETAIN",
kind: "executor",
ownerKey: "executor:FN-RETAIN",
});
writeFileSync(join(pinnedPath, "artifact"), "fresh\n", "utf-8");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "unregistered",
reason: "not registered in git worktree list",
});
const result = await acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
});
const generatedDirectories = readdirSync(recoveryRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && /^fn-\d+-[0-9a-f-]{36}$/.test(entry.name))
.map((entry) => entry.name);
expect(result.worktreePath).toBe(pinnedPath);
expect(generatedDirectories).toHaveLength(11);
expect(generatedDirectories).toContain(seeded[0]);
expect(generatedDirectories).not.toContain(seeded[1]);
expect(existsSync(unknownPath)).toBe(true);
expect(existsSync(generatedSymlink)).toBe(true);
});
it("retains only the newest ten generated orphan directories in the EXDEV fallback root", async () => {
const rootDir = makeRepo();
const externalWorktrees = track(mkdtempSync(join(tmpdir(), "fn-external-retention-worktrees-")));
const pinnedPath = join(externalWorktrees, "fn-1");
const recoveryRoot = join(externalWorktrees, ".fusion-recovery", "worktrees");
const seeded = seedPreservedOrphans(recoveryRoot, 11);
const unknownPath = join(recoveryRoot, "operator-notes");
mkdirSync(pinnedPath, { recursive: true });
mkdirSync(unknownPath);
activeSessionRegistry.registerPath(join(realpathSync(recoveryRoot), seeded[0]), {
taskId: "FN-RETAIN-EXDEV",
kind: "executor",
ownerKey: "executor:FN-RETAIN-EXDEV",
});
writeFileSync(join(pinnedPath, "artifact"), "fresh\n", "utf-8");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
});
const actualFs = await vi.importActual<typeof import("node:fs/promises")>("node:fs/promises");
const renameWorktreeDirectory = vi.fn()
.mockRejectedValueOnce(Object.assign(new Error("cross-device link"), { code: "EXDEV" }))
.mockImplementationOnce(actualFs.rename);
const result = await acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", worktreesDir: externalWorktrees, recycleWorktrees: false },
renameWorktreeDirectory,
});
const generatedDirectories = readdirSync(recoveryRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && /^fn-\d+-[0-9a-f-]{36}$/.test(entry.name))
.map((entry) => entry.name);
expect(result.worktreePath).toBe(pinnedPath);
expect(generatedDirectories).toHaveLength(11);
expect(generatedDirectories).toContain(seeded[0]);
expect(generatedDirectories).not.toContain(seeded[1]);
expect(existsSync(unknownPath)).toBe(true);
});
it("continues recreation when orphan-preservation audit and task logging fail after rename", async () => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
mkdirSync(pinnedPath, { recursive: true });
writeFileSync(join(pinnedPath, "artifact"), "stale\n", "utf-8");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
});
const auditFilesystem = vi.fn().mockRejectedValue(new Error("audit unavailable"));
const loggerWarn = vi.fn();
store.logEntry.mockImplementation(async (_taskId: string, message: string) => {
if (message.includes("Preserved orphaned task-pinned directory")) throw new Error("task log unavailable");
});
const result = await acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
audit: { filesystem: auditFilesystem, git: vi.fn().mockResolvedValue(undefined) } as any,
logger: { log: vi.fn(), warn: loggerWarn },
});
expect(result.worktreePath).toBe(pinnedPath);
expect(existsSync(join(pinnedPath, ".git"))).toBe(true);
expect(auditFilesystem).toHaveBeenCalledTimes(1);
expect(store.logEntry).toHaveBeenCalledWith(
"FN-1",
expect.stringContaining("Preserved orphaned task-pinned directory"),
expect.any(String),
undefined,
);
expect(loggerWarn).toHaveBeenCalledWith(expect.stringContaining("audit unavailable"));
expect(loggerWarn).toHaveBeenCalledWith(expect.stringContaining("task log unavailable"));
expect(loggerWarn).not.toHaveBeenCalledWith(expect.stringContaining("[object Object]"));
});
it("reconciles a durable quarantine for an absent pinned path before recreation", async () => {
const rootDir = makeRepo();
const worktreesDir = join(rootDir, ".worktrees");
const pinnedPath = join(worktreesDir, "fn-1");
mkdirSync(worktreesDir, { recursive: true });
git(rootDir, `git worktree add -b fusion/fn-1 ${JSON.stringify(pinnedPath)} main`);
rmSync(pinnedPath, { recursive: true, force: true });
const failedArchiveReservation = await acquireWorktreePathReservation({
canonicalPath: pinnedPath,
worktreesDir,
rootDir,
});
await failedArchiveReservation.quarantine("simulated archive failure");
const result = await acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
});
expect(result.worktreePath).toBe(pinnedPath);
expect(existsSync(join(pinnedPath, ".git"))).toBe(true);
expect(git(rootDir, "git worktree list --porcelain")).toContain(pinnedPath);
});
it("refuses quarantine reconciliation when an active session owns the absent pinned path", async () => {
const rootDir = makeRepo();
const worktreesDir = join(rootDir, ".worktrees");
const pinnedPath = join(worktreesDir, "fn-1");
mkdirSync(worktreesDir, { recursive: true });
const failedArchiveReservation = await acquireWorktreePathReservation({
canonicalPath: pinnedPath,
worktreesDir,
rootDir,
});
await failedArchiveReservation.quarantine("simulated archive failure");
activeSessionRegistry.registerPath(pinnedPath, { taskId: "FN-2", kind: "executor", ownerKey: "executor:FN-2" });
const createWorktree = vi.fn();
await expect(acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
createWorktree,
})).rejects.toThrow(`Refusing to reconcile absent task-pinned worktree owned by an active session: ${pinnedPath}`);
expect(createWorktree).not.toHaveBeenCalled();
expect(existsSync(pinnedPath)).toBe(false);
});
it("preserves the orphan in place when its path becomes active during recovery", async () => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
mkdirSync(join(pinnedPath, ".build"), { recursive: true });
writeFileSync(join(pinnedPath, ".build", "cache"), "stale\n", "utf-8");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
});
let becameActive = false;
vi.spyOn(activeSessionRegistry, "isPathActive").mockImplementation((path: string) => {
if (path !== pinnedPath) return false;
if (!becameActive) {
becameActive = true;
return false;
}
return true;
});
await expect(acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
})).rejects.toThrow(/became active/);
expect(readFileSync(join(pinnedPath, ".build", "cache"), "utf-8")).toBe("stale\n");
expect(existsSync(join(rootDir, ".fusion", "recovery", "worktrees"))).toBe(true);
expect(readdirSync(join(rootDir, ".fusion", "recovery", "worktrees"))).toHaveLength(0);
});
it("refuses to preserve an orphan through a recovery-directory symlink", async () => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
const outside = track(mkdtempSync(join(tmpdir(), "fn-orphan-recovery-outside-")));
mkdirSync(join(pinnedPath, ".build"), { recursive: true });
writeFileSync(join(pinnedPath, ".build", "cache"), "stale\n", "utf-8");
mkdirSync(join(rootDir, ".fusion", "recovery"), { recursive: true });
symlinkSync(outside, join(rootDir, ".fusion", "recovery", "worktrees"));
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
});
await expect(acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
})).rejects.toThrow(`Refusing to use recovery directory outside ${realpathSync(join(rootDir, ".fusion", "recovery"))}`);
expect(readFileSync(join(pinnedPath, ".build", "cache"), "utf-8")).toBe("stale\n");
expect(readdirSync(outside)).toHaveLength(0);
});
it("refuses to create recovery contents through an ancestor symlink", async () => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
const outside = track(mkdtempSync(join(tmpdir(), "fn-orphan-recovery-ancestor-outside-")));
mkdirSync(join(pinnedPath, ".build"), { recursive: true });
writeFileSync(join(pinnedPath, ".build", "cache"), "stale\n", "utf-8");
mkdirSync(join(rootDir, ".fusion"), { recursive: true });
symlinkSync(outside, join(rootDir, ".fusion", "recovery"));
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
});
await expect(acquireTaskWorktree({
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
})).rejects.toThrow(`Refusing to use recovery directory outside ${realpathSync(join(rootDir, ".fusion"))}`);
expect(readFileSync(join(pinnedPath, ".build", "cache"), "utf-8")).toBe("stale\n");
expect(readdirSync(outside)).toHaveLength(0);
});
it("serializes concurrent orphan recovery through fresh recreation", async () => {
const rootDir = makeRepo();
const pinnedPath = join(rootDir, ".worktrees", "fn-1");
mkdirSync(join(pinnedPath, ".build"), { recursive: true });
writeFileSync(join(pinnedPath, ".build", "cache"), "stale\n", "utf-8");
const actualPool = await vi.importActual<typeof import("../worktree/worktree-pool.js")>("../worktree/worktree-pool.js");
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({
ok: false,
classification: "incomplete",
reason: "missing .git metadata",
}).mockImplementation(actualPool.classifyTaskWorktree);
let signalCreateStarted!: () => void;
const createStarted = new Promise<void>((resolve) => { signalCreateStarted = resolve; });
let allowCreate!: () => void;
const createGate = new Promise<void>((resolve) => { allowCreate = resolve; });
const createWorktree = vi.fn(async (branch: string, path: string) => {
signalCreateStarted();
await createGate;
git(rootDir, `git worktree add -b ${JSON.stringify(branch)} ${JSON.stringify(path)} main`);
return { path, branch };
});
const input = {
task: { ...task, worktree: pinnedPath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
createWorktree,
} as const;
const first = acquireTaskWorktree(input);
await createStarted;
const second = acquireTaskWorktree(input);
allowCreate();
const [firstResult, secondResult] = await Promise.all([first, second]);
expect(firstResult.worktreePath).toBe(pinnedPath);
expect(secondResult.worktreePath).toBe(pinnedPath);
expect(createWorktree).toHaveBeenCalledTimes(1);
expect(readFileSync(join(rootDir, ".fusion", "recovery", "worktrees", readdirSync(join(rootDir, ".fusion", "recovery", "worktrees"))[0], ".build", "cache"), "utf-8")).toBe("stale\n");
});
it("refreshes a recreated existing task branch after its dependency branch is deleted", async () => {
const rootDir = makeRepo();
const staleBase = git(rootDir, "git rev-parse HEAD");
git(rootDir, `git branch fusion/fn-4 ${staleBase}`);
git(rootDir, `git checkout -b fusion/deleted-dependency ${staleBase}`);
writeFileSync(join(rootDir, "dependency-output.ts"), "export const dependencyOutput = true;\n", "utf-8");
git(rootDir, "git add dependency-output.ts");
git(rootDir, 'git commit -m "land dependency"');
git(rootDir, "git checkout main");
git(rootDir, "git merge --ff-only fusion/deleted-dependency");
git(rootDir, "git branch -d fusion/deleted-dependency");
const landedBase = git(rootDir, "git rev-parse HEAD");
const result = await acquireTaskWorktree({
task: {
...task,
id: "FN-4",
branch: "fusion/fn-4",
baseCommitSha: staleBase,
executionStartBranch: "fusion/deleted-dependency",
},
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false },
refreshStaleBase: true,
createWorktree: async (branch, path) => {
git(rootDir, `git worktree add ${JSON.stringify(path)} ${JSON.stringify(branch)}`);
return { path, branch };
},
});
expect(result).toMatchObject({
source: "fresh",
baseRefresh: { kind: "reset-to-base", executionSafe: true, baseSha: landedBase },
});
expect(git(result.worktreePath, "git rev-parse HEAD")).toBe(landedBase);
expect(existsSync(join(result.worktreePath, "dependency-output.ts"))).toBe(true);
expect(store.updateTask).toHaveBeenCalledWith("FN-4", { baseCommitSha: landedBase });
});
it("refreshes a retained task branch acquired from the worktree pool", async () => {
const rootDir = makeRepo();
const staleBase = git(rootDir, "git rev-parse HEAD");
const pooledPath = join(rootDir, ".worktrees", "pooled-fn-4");
git(rootDir, `git worktree add -b fusion/fn-4 ${JSON.stringify(pooledPath)} ${staleBase}`);
writeFileSync(join(rootDir, "dependency-output.ts"), "export const dependencyOutput = true;\n", "utf-8");
git(rootDir, "git add dependency-output.ts");
git(rootDir, 'git commit -m "land dependency"');
const landedBase = git(rootDir, "git rev-parse HEAD");
const pool = {
acquire: vi.fn().mockReturnValue(pooledPath),
prepareForTask: vi.fn().mockResolvedValue({
branch: "fusion/fn-4",
worktreePath: pooledPath,
reclaimed: false,
}),
release: vi.fn(),
} as any;
const result = await acquireTaskWorktree({
task: { ...task, id: "FN-4", branch: "fusion/fn-4", baseCommitSha: staleBase },
rootDir,
store,
settings: { recycleWorktrees: true },
pool,
refreshStaleBase: true,
});
expect(result).toMatchObject({
source: "pool",
baseRefresh: { kind: "reset-to-base", executionSafe: true, baseSha: landedBase },
});
expect(git(pooledPath, "git rev-parse HEAD")).toBe(landedBase);
expect(existsSync(join(pooledPath, "dependency-output.ts"))).toBe(true);
});
it("does not apply the native stale-base refresh to fresh Worktrunk acquisitions", async () => {
const result = await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { worktrunk: { enabled: true } } as any,
backend: { kind: "worktrunk" } as any,
createWorktreeBackendKind: "worktrunk",
refreshStaleBase: true,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/worktrunk-fresh", branch: "fusion/fn-1" }),
});
expect(result).toMatchObject({ source: "fresh", baseRefresh: undefined });
});
it("persists the backend used by an internally created worktree", async () => {
const rootDir = makeRepo();
const result = await acquireTaskWorktree({
task,
rootDir,
store,
settings: { recycleWorktrees: false },
backend: new NativeWorktreeBackend(),
});
const markerPath = git(result.worktreePath, "git rev-parse --git-path fusion-worktree-backend-kind");
expect(readFileSync(markerPath, "utf-8")).toBe("native\n");
});
it("refreshes a native fallback acquisition even when Worktrunk is enabled", async () => {
const rootDir = makeRepo();
const staleBase = git(rootDir, "git rev-parse HEAD");
git(rootDir, `git branch fusion/fn-4 ${staleBase}`);
writeFileSync(join(rootDir, "dependency-output.ts"), "export const dependencyOutput = true;\n", "utf-8");
git(rootDir, "git add dependency-output.ts");
git(rootDir, 'git commit -m "land dependency"');
const landedBase = git(rootDir, "git rev-parse HEAD");
const result = await acquireTaskWorktree({
task: { ...task, id: "FN-4", branch: "fusion/fn-4", baseCommitSha: staleBase },
rootDir,
store,
settings: { worktreeNaming: "task-id", recycleWorktrees: false, worktrunk: { enabled: true } } as any,
backend: { kind: "worktrunk" } as any,
createWorktreeBackendKind: "native",
refreshStaleBase: true,
createWorktree: async (branch, path) => {
git(rootDir, `git worktree add ${JSON.stringify(path)} ${JSON.stringify(branch)}`);
return { path, branch };
},
});
expect(result).toMatchObject({
source: "fresh",
baseRefresh: { kind: "reset-to-base", executionSafe: true, baseSha: landedBase },
});
});
it("uses the persisted native backend when a Worktrunk fallback is reused", async () => {
const rootDir = makeRepo();
const staleBase = git(rootDir, "git rev-parse HEAD");
const worktreePath = join(rootDir, ".worktrees", "fallback-fn-4");
git(rootDir, `git worktree add -b fusion/fn-4 ${JSON.stringify(worktreePath)} ${staleBase}`);
const markerPath = git(worktreePath, "git rev-parse --git-path fusion-worktree-backend-kind");
writeFileSync(markerPath, "native\n", "utf-8");
writeFileSync(join(rootDir, "dependency-output.ts"), "export const dependencyOutput = true;\n", "utf-8");
git(rootDir, "git add dependency-output.ts");
git(rootDir, 'git commit -m "land dependency"');
const landedBase = git(rootDir, "git rev-parse HEAD");
const result = await acquireTaskWorktree({
task: { ...task, id: "FN-4", worktree: worktreePath, branch: "fusion/fn-4", baseCommitSha: staleBase },
rootDir,
store,
settings: { worktrunk: { enabled: true } } as any,
backend: { kind: "worktrunk" } as any,
refreshStaleBase: true,
});
expect(result).toMatchObject({
source: "existing",
baseRefresh: { kind: "reset-to-base", executionSafe: true, baseSha: landedBase },
});
expect(git(worktreePath, "git rev-parse HEAD")).toBe(landedBase);
});
it("preserves a persisted Worktrunk backend when an injected native creator is available", async () => {
const rootDir = makeRepo();
const staleBase = git(rootDir, "git rev-parse HEAD");
const worktreePath = join(rootDir, ".worktrees", "worktrunk-fn-4");
git(rootDir, `git worktree add -b fusion/fn-4 ${JSON.stringify(worktreePath)} ${staleBase}`);
const markerPath = git(worktreePath, "git rev-parse --git-path fusion-worktree-backend-kind");
writeFileSync(markerPath, "worktrunk\n", "utf-8");
writeFileSync(join(rootDir, "dependency-output.ts"), "export const dependencyOutput = true;\n", "utf-8");
git(rootDir, "git add dependency-output.ts");
git(rootDir, 'git commit -m "land dependency"');
const result = await acquireTaskWorktree({
task: { ...task, id: "FN-4", worktree: worktreePath, branch: "fusion/fn-4", baseCommitSha: staleBase },
rootDir,
store,
settings: { worktrunk: { enabled: true } } as any,
backend: { kind: "worktrunk" } as any,
createWorktreeBackendKind: "native",
refreshStaleBase: true,
});
expect(result).toMatchObject({ source: "existing", baseRefresh: undefined });
expect(git(worktreePath, "git rev-parse HEAD")).toBe(staleBase);
});
/*
FNXC:WorktreeBaseRefresh 2026-08-09-23:49:
The invariant here is the RESOURCE-HYGIENE ordering — the task binding is cleared before the worktree goes
back to the pool — not the refusal policy that used to trigger it. A dirty checkout no longer fails base
refresh (it declines and executes on the existing base), and in production a pooled worktree never reaches
the refresh dirty anyway: `prepareForTask` runs `git checkout -- .` + `git clean -fd` first, so the old
fixture's dirt only survived because the pool is mocked here. Drive the ordering through the secrets-record
reconciliation refusal instead, which is a secrets-safety gate and remains unconditionally blocking.
*/
it("clears a pooled task binding before releasing a worktree that fails base refresh", async () => {
const rootDir = makeRepo();
const pooledPath = join(rootDir, ".worktrees", "pooled-fn-4-dirty");
git(rootDir, `git worktree add -b fusion/fn-4-dirty ${JSON.stringify(pooledPath)}`);
// A malformed root secrets-env record: reconciliation cannot prove the checkout is safe to hand an agent.
writeFileSync(join(pooledPath, ".fusion-secrets-env.fingerprint"), "not-a-valid-record\n", "utf-8");
const pool = {
acquire: vi.fn().mockReturnValue(pooledPath),
prepareForTask: vi.fn().mockResolvedValue({
branch: "fusion/fn-4-dirty",
worktreePath: pooledPath,
reclaimed: false,
}),
release: vi.fn(),
} as any;
await expect(acquireTaskWorktree({
task: { ...task, id: "FN-4", branch: "fusion/fn-4-dirty" },
rootDir,
store,
settings: { recycleWorktrees: true },
pool,
refreshStaleBase: true,
})).rejects.toThrow(WorktreeBaseRefreshError);
expect(store.updateTask).toHaveBeenCalledWith("FN-4", { worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
expect(store.updateTask.mock.invocationCallOrder.at(-1)).toBeLessThan(pool.release.mock.invocationCallOrder[0]);
});
it("FN-6861 creates a fresh configured worktree when a resumed assignment points at the repo root", async () => {
const rootDir = makeRepo();
const actualPool = await vi.importActual<typeof import("../worktree/worktree-pool.js")>("../worktree/worktree-pool.js");
vi.mocked(classifyTaskWorktree).mockImplementationOnce(actualPool.classifyTaskWorktree);
const freshPath = join(rootDir, ".worktrees", "fn-6861-fresh");
const createWorktree = vi.fn().mockResolvedValue({ path: freshPath, branch: "fusion/fn-1" });
const auditGit = vi.fn().mockResolvedValue(undefined);
const result = await acquireTaskWorktree({
task: { ...task, worktree: rootDir, branch: "fusion/fn-1", sessionFile: "/tmp/session.json" },
rootDir,
store,
settings: {} as any,
createWorktree,
audit: { git: auditGit } as any,
});
expect(result).toMatchObject({
worktreePath: freshPath,
branch: "fusion/fn-1",
source: "fresh",
isResume: false,
});
expect(result.worktreePath).not.toBe(rootDir);
expect(result.worktreePath).toContain(`${join(rootDir, ".worktrees")}/`);
expect(auditGit).toHaveBeenCalledWith(expect.objectContaining({
type: "worktree:incomplete-detected",
target: rootDir,
metadata: expect.objectContaining({ classification: "repo-root", source: "resume" }),
}));
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: freshPath, branch: "fusion/fn-1", branchWriteOrigin: "engine" });
});
it("FN-6922 rejects a canonical-equal resumed repo root before returning", async () => {
const rootDir = makeRepo();
const actualPool = await vi.importActual<typeof import("../worktree/worktree-pool.js")>("../worktree/worktree-pool.js");
vi.mocked(classifyTaskWorktree).mockImplementationOnce(actualPool.classifyTaskWorktree);
const freshPath = join(rootDir, ".worktrees", "fn-6922-trailing-slash");
const createWorktree = vi.fn().mockResolvedValue({ path: freshPath, branch: "fusion/fn-1" });
const result = await acquireTaskWorktree({
task: { ...task, worktree: `${rootDir}/`, branch: "fusion/fn-1", sessionFile: "/tmp/session.json" },
rootDir,
store,
settings: {} as any,
createWorktree,
});
expect(result.worktreePath).toBe(freshPath);
expect(result.worktreePath).not.toBe(rootDir);
expect(result.isResume).toBe(false);
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: freshPath, branch: "fusion/fn-1", branchWriteOrigin: "engine" });
});
it("FN-6922 self-heals when the return guard catches a mocked repo-root resume", async () => {
const rootDir = makeRepo();
vi.mocked(classifyTaskWorktree).mockResolvedValueOnce({ ok: true });
const freshPath = join(rootDir, ".worktrees", "fn-6922-guard-fresh");
const createWorktree = vi.fn().mockResolvedValue({ path: freshPath, branch: "fusion/fn-1" });
const auditGit = vi.fn().mockResolvedValue(undefined);
const result = await acquireTaskWorktree({
task: { ...task, worktree: rootDir, branch: "fusion/fn-1", sessionFile: "/tmp/session.json" },
rootDir,
store,
settings: {} as any,
createWorktree,
audit: { git: auditGit } as any,
});
expect(result).toMatchObject({ worktreePath: freshPath, source: "fresh", isResume: false });
expect(createWorktree).toHaveBeenCalledWith("fusion/fn-1", expect.stringContaining(`${join(rootDir, ".worktrees")}/`), "FN-1", "main", false);
expect(auditGit).toHaveBeenCalledWith(expect.objectContaining({
type: "worktree:incomplete-detected",
target: rootDir,
metadata: expect.objectContaining({ classification: "repo-root", source: "acquire-return-guard", returnSource: "existing" }),
}));
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: freshPath, branch: "fusion/fn-1", branchWriteOrigin: "engine" });
});
it("FN-6922 throws a typed error when fresh creation returns the repo root", async () => {
const rootDir = makeRepo();
const auditGit = vi.fn().mockResolvedValue(undefined);
await expect(acquireTaskWorktree({
task: { ...task, worktree: null, branch: null },
rootDir,
store,
settings: {} as any,
createWorktree: vi.fn().mockResolvedValue({ path: rootDir, branch: "fusion/fn-1" }),
audit: { git: auditGit } as any,
})).rejects.toBeInstanceOf(RepoRootWorktreeError);
expect(auditGit).toHaveBeenCalledWith(expect.objectContaining({
type: "worktree:incomplete-detected",
target: rootDir,
metadata: expect.objectContaining({ classification: "repo-root", source: "acquire-return-guard", returnSource: "fresh" }),
}));
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
});
it("falls through to fresh creation when pool acquire throws PoolDoubleLeaseError", async () => {
const createWorktree = vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" });
const result = await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { recycleWorktrees: true } as any,
pool: {
acquire: () => {
throw new PoolDoubleLeaseError("/tmp/pooled", "FN-OTHER", "FN-1", "acquire");
},
prepareForTask: vi.fn(),
release: vi.fn(),
} as any,
createWorktree,
logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
expect(result.source).toBe("fresh");
expect(createWorktree).toHaveBeenCalled();
expect(store.logEntry).toHaveBeenCalledWith("FN-1", expect.stringContaining("Pool double-lease guard triggered"), undefined, undefined);
});
it("creates fresh when pool disabled", async () => {
const createWorktree = vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" });
const result = await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: {},
createWorktree,
});
expect(result.source).toBe("fresh");
expect(createWorktree).toHaveBeenCalled();
expect(store.updateTask).toHaveBeenCalledWith("FN-1", { worktree: "/tmp/new", branch: "fusion/fn-1", branchWriteOrigin: "engine" });
});
it("skips init command when runInitCommand false", async () => {
const runConfiguredCommand = vi.fn();
await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { worktreeInitCommand: "pnpm i" } as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" }),
runConfiguredCommand,
runInitCommand: false,
});
expect(runConfiguredCommand).not.toHaveBeenCalled();
});
it("invokes desktop artifact cleanup before init command for fresh acquisition", async () => {
const runConfiguredCommand = vi.fn().mockResolvedValue({ exitCode: 0, stderr: "", stdout: "" });
await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { worktreeInitCommand: "pnpm install" } as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" }),
runConfiguredCommand,
runInitCommand: true,
});
expect(desktopArtifacts.removeDesktopBuildArtifacts).toHaveBeenCalledWith("/tmp/new", undefined);
const cleanupOrder = vi.mocked(desktopArtifacts.removeDesktopBuildArtifacts).mock.invocationCallOrder[0];
const initOrder = runConfiguredCommand.mock.invocationCallOrder[0];
expect(cleanupOrder).toBeLessThan(initOrder);
});
it("copies configured files for fresh acquisition before init command", async () => {
const rootDir = track(mkdtempSync(join(tmpdir(), "fn-copy-fresh-root-")));
const worktreePath = track(mkdtempSync(join(tmpdir(), "fn-copy-fresh-worktree-")));
writeFileSync(join(rootDir, ".env"), "SECRET=redacted\n", "utf-8");
const runConfiguredCommand = vi.fn().mockImplementation(async () => {
expect(readFileSync(join(worktreePath, ".env"), "utf-8")).toBe("SECRET=redacted\n");
return { exitCode: 0, stderr: "", stdout: "" };
});
await acquireTaskWorktree({
task,
rootDir,
store,
settings: { worktreeCopyFiles: [".env"], worktreeInitCommand: "pnpm install" } as any,
createWorktree: vi.fn().mockResolvedValue({ path: worktreePath, branch: "fusion/fn-1" }),
runConfiguredCommand,
runInitCommand: true,
});
expect(readFileSync(join(worktreePath, ".env"), "utf-8")).toBe("SECRET=redacted\n");
expect(store.logEntry).toHaveBeenCalledWith("FN-1", "Copied configured worktree files into fresh worktree: .env", undefined, undefined);
});
it("invokes desktop artifact cleanup once for pooled acquisition", async () => {
const runConfiguredCommand = vi.fn();
const pooledPath = track(mkdtempSync(join(tmpdir(), "fn-pooled-cleanup-")));
await acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { recycleWorktrees: true, worktreeInitCommand: "pnpm install" } as any,
pool: {
acquire: (_taskId: string) => pooledPath,
prepareForTask: vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath: pooledPath, reclaimed: false }),
release: vi.fn(),
} as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
runConfiguredCommand,
runInitCommand: true,
});
expect(desktopArtifacts.removeDesktopBuildArtifacts).toHaveBeenCalledTimes(1);
expect(desktopArtifacts.removeDesktopBuildArtifacts).toHaveBeenCalledWith(pooledPath, undefined);
expect(runConfiguredCommand).not.toHaveBeenCalled();
});
it("copies configured files into pooled worktrees after preparation", async () => {
const rootDir = track(mkdtempSync(join(tmpdir(), "fn-copy-pool-root-")));
const worktreePath = track(mkdtempSync(join(tmpdir(), "fn-copy-pool-worktree-")));
writeFileSync(join(rootDir, ".env"), "POOL=updated\n", "utf-8");
writeFileSync(join(worktreePath, ".env"), "POOL=old\n", "utf-8");
const result = await acquireTaskWorktree({
task,
rootDir,
store,
settings: { recycleWorktrees: true, worktreeCopyFiles: [".env"] } as any,
pool: {
acquire: (_taskId: string) => worktreePath,
prepareForTask: vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath, reclaimed: false }),
release: vi.fn(),
} as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(result.source).toBe("pool");
expect(readFileSync(join(worktreePath, ".env"), "utf-8")).toBe("POOL=updated\n");
expect(store.logEntry).toHaveBeenCalledWith("FN-1", "Copied configured worktree files into pool worktree: .env", undefined, undefined);
});
it("does not copy configured files over resumed worktree state", async () => {
const rootDir = track(mkdtempSync(join(tmpdir(), "fn-copy-resume-root-")));
const worktreePath = track(mkdtempSync(join(tmpdir(), "fn-copy-resume-worktree-")));
writeFileSync(join(rootDir, ".env"), "ROOT=updated\n", "utf-8");
writeFileSync(join(worktreePath, ".env"), "RESUME=keep\n", "utf-8");
const result = await acquireTaskWorktree({
task: { ...task, worktree: worktreePath, branch: "fusion/fn-1" },
rootDir,
store,
settings: { worktreeCopyFiles: [".env"] } as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/fn-worktree-fallback", branch: "fusion/fn-1" }),
});
expect(result.source).toBe("existing");
expect(readFileSync(join(worktreePath, ".env"), "utf-8")).toBe("RESUME=keep\n");
expect(store.logEntry).not.toHaveBeenCalledWith("FN-1", expect.stringContaining("Copied configured worktree files"), undefined, undefined);
expect(existsSync(join(worktreePath, ".env"))).toBe(true);
});
it("FN-4834: logs worktree init stderr in task log outcome", async () => {
const runConfiguredCommand = vi.fn().mockResolvedValue({
exitCode: 1,
stderr: "ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE Cannot install with \"frozen-lockfile\" because pnpm-lock.yaml is not up to date",
stdout: "",
});
await expect(acquireTaskWorktree({
task,
rootDir: process.cwd(),
store,
settings: { worktreeInitCommand: "pnpm install --frozen-lockfile" } as any,
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/new", branch: "fusion/fn-1" }),
runConfiguredCommand,
runInitCommand: true,
logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() },
})).resolves.toBeTruthy();
const failureCall = store.logEntry.mock.calls.find((call: unknown[]) => String(call[1]).startsWith("Worktree init command failed"));
expect(failureCall).toBeDefined();
expect(failureCall?.[2]).toContain("ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE");
});
});
describe("acquireTaskWorktree foreign start-point warning", () => {
it("emits warning/log for fusion/fn-* start point with foreign-attributed tip and stays silent for main", async () => {
vi.resetModules();
const warn = vi.fn();
const logEntry = vi.fn().mockResolvedValue(undefined);
const execMock: any = (_command: string, _opts: any, cb: any) => cb(null, "", "");
execMock[promisify.custom] = (command: string) => {
if (command.startsWith("git rev-parse --verify \"fusion/fn-4367^")) {
return Promise.resolve({ stdout: "deadbeefdeadbeef\n", stderr: "" });
}
if (command.startsWith("git log -1 --format=%s%x1f%b")) {
return Promise.resolve({ stdout: "feat(FN-4367): dep\u001fFusion-Task-Id: FN-4367\n", stderr: "" });
}
return Promise.resolve({ stdout: "", stderr: "" });
};
vi.doMock("node:child_process", () => ({ exec: execMock, execFile: execMock }));
const mod = await import("../worktree/worktree-acquisition.js");
await mod.acquireTaskWorktree({
task: { id: "FN-4488", title: "Task", description: "Desc", branch: null, worktree: null, executionStartBranch: "fusion/fn-4367" } as any,
rootDir: "/tmp/repo",
store: { updateTask: vi.fn().mockResolvedValue(undefined), logEntry } as any,
settings: {},
logger: { log: vi.fn(), warn, error: vi.fn() },
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/repo/.worktrees/x", branch: "fusion/fn-4488" }),
});
expect(warn).toHaveBeenCalledWith(expect.stringContaining("worktree acquired with foreign-task start point: fusion/fn-4367"));
expect(logEntry).toHaveBeenCalledWith("FN-4488", expect.stringContaining("worktree acquired with foreign-task start point: fusion/fn-4367"), undefined, undefined);
warn.mockClear();
logEntry.mockClear();
await mod.acquireTaskWorktree({
task: { id: "FN-4488", title: "Task", description: "Desc", branch: null, worktree: null, executionStartBranch: "main" } as any,
rootDir: "/tmp/repo",
store: { updateTask: vi.fn().mockResolvedValue(undefined), logEntry } as any,
settings: {},
logger: { log: vi.fn(), warn, error: vi.fn() },
createWorktree: vi.fn().mockResolvedValue({ path: "/tmp/repo/.worktrees/x", branch: "fusion/fn-4488" }),
});
expect(warn).not.toHaveBeenCalled();
expect(logEntry).not.toHaveBeenCalledWith("FN-4488", expect.stringContaining("foreign-task start point"), undefined, undefined);
});
});