fix(FN-4948): harden guard install failure cleanup paths

Fusion-Task-Id: FN-4948
Fusion-Task-Lineage: dc622643-4c3e-4217-9219-a6a6e5424427
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 13:31:54 -07:00
committed by gsxdsm
parent 487dfcc10c
commit 4b16429618
5 changed files with 80 additions and 13 deletions

View File

@@ -9,6 +9,7 @@ import {
import { AgentLogger } from "../agent-logger.js";
import * as worktreeBackendModule from "../worktree-backend.js";
import type { TaskDetail, Settings, TaskStore } from "@fusion/core";
import { installTaskWorktreeIdentityGuard } from "../worktree-hooks.js";
vi.mock("../worktree-hooks.js", () => ({
installTaskWorktreeIdentityGuard: vi.fn().mockResolvedValue(undefined),
@@ -692,6 +693,7 @@ import { createLogger } from "../logger.js";
const mockedCreateFnAgent = vi.mocked(createFnAgent);
const mockedExecSync = vi.mocked(execSync);
const mockedInstallTaskWorktreeIdentityGuard = vi.mocked(installTaskWorktreeIdentityGuard);
const mockedGenerateWorktreeName = vi.mocked(generateWorktreeName);
const mockedCreateLogger = vi.mocked(createLogger);
@@ -1171,6 +1173,7 @@ describe("StepSessionExecutor", () => {
expect.stringContaining("git worktree add"),
expect.anything(),
);
expect(mockedInstallTaskWorktreeIdentityGuard).toHaveBeenCalled();
});
it("handles parallel step failure: successful step cherry-picked, failed cleaned up", async () => {

View File

@@ -86,6 +86,25 @@ describe("NativeWorktreeBackend", () => {
expect(installGuardMock).toHaveBeenCalledWith({ worktreePath: "/repo/.worktrees/fn-1", taskId: "FN-1" });
});
it("propagates installer failure after cleanup", async () => {
execMock.mockResolvedValue({ stdout: "", stderr: "" });
installGuardMock.mockRejectedValueOnce(new Error("guard failed"));
await expect(
new NativeWorktreeBackend().create({
rootDir: "/repo",
worktreePath: "/repo/.worktrees/fn-1",
branch: "fusion/fn-1",
taskId: "FN-1",
}),
).rejects.toThrow("guard failed");
expect(execMock).toHaveBeenCalledWith(
'rm -rf "/repo/.worktrees/fn-1"',
expect.objectContaining({ cwd: "/repo", timeout: 60000, maxBuffer: 10485760 }),
);
});
it("retries with suffix and resolves", async () => {
execMock.mockRejectedValueOnce(new Error("exists")).mockResolvedValueOnce({ stdout: "", stderr: "" });