diff --git a/packages/engine/src/__tests__/workflow-task-runtime.test.ts b/packages/engine/src/__tests__/workflow-task-runtime.test.ts index 71bd14bcae..ea4d7282d4 100644 --- a/packages/engine/src/__tests__/workflow-task-runtime.test.ts +++ b/packages/engine/src/__tests__/workflow-task-runtime.test.ts @@ -29,7 +29,9 @@ function selectedIr(): WorkflowIr { function recordingPrimitives( calls: string[], - overrides: Partial> = {}, + overrides: Partial> & { + prepareData?: PreparedWorktree | null; + } = {}, observed: { prepared?: PreparedWorktree } = {}, ): WorkflowRuntimePrimitives { const prepared: PreparedWorktree = { worktreePath: "/tmp/fusion-worktree" }; @@ -40,7 +42,11 @@ function recordingPrimitives( outcome: overrides.prepare?.outcome ?? "success", value: overrides.prepare?.value, contextPatch: overrides.prepare?.contextPatch, - data: overrides.prepare?.outcome === "failure" ? undefined : prepared, + data: overrides.prepare?.outcome === "failure" + ? undefined + : overrides.prepareData === null + ? undefined + : overrides.prepareData ?? prepared, }; }, readArtifact: async () => undefined, @@ -145,6 +151,30 @@ describe("WorkflowTaskRuntime", () => { expect(workflowSelectionReads).toBe(1); }); + it("fails execute instead of skipping coding when prepare succeeds without worktree data", async () => { + const calls: string[] = []; + const runtime = new WorkflowTaskRuntime({ + store: { + getTaskWorkflowSelection: () => ({ workflowId: "WF-001", stepIds: [] }), + getWorkflowDefinition: async () => ({ ir: selectedIr() }), + }, + primitives: recordingPrimitives(calls, { + prepare: { outcome: "success", value: "prepared-without-data" }, + prepareData: null, + }), + runCustomNode: async (node) => { + calls.push(`custom:${node.id}`); + return { outcome: "success" }; + }, + }); + + const result = await runtime.run(task, flagOff); + + expect(result.disposition).toBe("failed"); + expect(calls).toEqual(["custom:prepare", "prepare-worktree"]); + expect(result.visitedNodeIds).toEqual(["start", "prepare", "execute"]); + }); + it("resolves an unselected task to the built-in coding workflow instead of falling back", async () => { const calls: string[] = []; const runtime = new WorkflowTaskRuntime({ diff --git a/packages/engine/src/workflow-node-handlers.ts b/packages/engine/src/workflow-node-handlers.ts index aef23816d6..ae62911aca 100644 --- a/packages/engine/src/workflow-node-handlers.ts +++ b/packages/engine/src/workflow-node-handlers.ts @@ -319,7 +319,7 @@ export function createPrimitivePromptLikeHandler( const prepared = await primitives.prepareWorktree(primitiveCtx, context.task); if (prepared.outcome !== "success" || !prepared.data) { return { - outcome: prepared.outcome, + outcome: prepared.outcome === "success" ? "failure" : prepared.outcome, value: prepared.value ?? "prepare-worktree-failed", contextPatch: prepared.contextPatch, };