Merge pull request #918 from abeperl/fix/fn-009-task-done-missing-worktree

fix: allow fn_task_done when worktree directory missing
This commit is contained in:
gsxdsm
2026-05-25 14:13:31 -07:00
committed by GitHub
3 changed files with 108 additions and 0 deletions

View File

@@ -293,6 +293,7 @@
### Patch Changes
- Fixed `fn_task_done` to gracefully handle missing worktree directories. When a task's worktree has been deleted (common for documentation/coordination tasks with no code changes), `fn_task_done` previously failed with ENOENT attempting to spawn git commands in non-existent directories. The fix adds an `existsSync` check in `verifyWorktreeInvariants` before executing git commands, allowing legitimate task completions to proceed. This is safe because task completion is read-only, deliverables are stored in fusion.db (not the worktree), and if code changes were made, the worktree would exist. Resolves infinite loops where agents couldn't complete tasks without shell access to missing directories.
- Updated dependencies [1f0bb7e]
- @fusion/core@0.32.0
- @fusion/pi-claude-cli@0.32.0

View File

@@ -0,0 +1,94 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import "./executor-test-helpers.js";
import { TaskExecutor } from "../executor.js";
import { createMockStore, mockedExistsSync, resetExecutorMocks } from "./executor-test-helpers.js";
describe("FN-009: verifyWorktreeInvariants with missing worktree directory", () => {
let executor: TaskExecutor;
let store: ReturnType<typeof createMockStore>;
beforeEach(() => {
resetExecutorMocks();
store = createMockStore();
executor = new TaskExecutor(store as any, "/repo");
});
it("returns success when worktree directory does not exist", async () => {
const task = {
id: "FN-9001",
title: "Test",
description: "Test",
column: "in-progress",
worktree: "/repo/.worktrees/missing",
branch: "fusion/fn-9001",
dependencies: [],
steps: [],
currentStep: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
// Mock existsSync to return false for the worktree path
mockedExistsSync.mockImplementation((path: any) => {
if (path === "/repo/.worktrees/missing") {
return false;
}
return true;
});
// Access the private method using type assertion
const result = await (executor as any).verifyWorktreeInvariants(task);
expect(result.ok).toBe(true);
});
it("does not skip validation when worktree directory exists", async () => {
const task = {
id: "FN-9002",
title: "Test",
description: "Test",
column: "in-progress",
worktree: "/repo/.worktrees/existing",
branch: "fusion/fn-9002",
dependencies: [],
steps: [],
currentStep: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
// Mock existsSync to return true for the worktree path
mockedExistsSync.mockReturnValue(true);
const result = await (executor as any).verifyWorktreeInvariants(task);
// When worktree exists, validation proceeds normally.
// The exact result depends on git command mocks, but we verify
// that the function doesn't return early with { ok: true } due to missing directory.
// We verify that existsSync was called by checking it was configured.
expect(mockedExistsSync).toHaveBeenCalled();
expect(result).toBeDefined();
});
it("preserves validation failure when worktree path is null", async () => {
const task = {
id: "FN-9003",
title: "Test",
description: "Test",
column: "in-progress",
worktree: null,
branch: "fusion/fn-9003",
dependencies: [],
steps: [],
currentStep: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = await (executor as any).verifyWorktreeInvariants(task);
expect(result.ok).toBe(false);
expect(result.reason).toBe("wrong_toplevel");
expect(result.observed).toContain("missing task.worktree");
});
});

View File

@@ -5788,6 +5788,19 @@ export class TaskExecutor {
};
}
// FN-009: If worktree directory doesn't exist, skip git validation for task completion.
// This is safe because:
// 1. Task completion doesn't modify the worktree
// 2. Deliverables (task documents, follow-up tasks) are stored in fusion.db
// 3. If code changes were made, the worktree would exist
// 4. This prevents ENOENT errors when agents complete documentation/coordination tasks
if (!existsSync(worktreePath)) {
executorLog.log(
`${task.id}: worktree directory not found at ${worktreePath} — skipping git validation for task completion`,
);
return { ok: true };
}
try {
const { stdout } = await execAsync("git rev-parse --show-toplevel", {
cwd: worktreePath,