From 234248a3cd1b2c68d7177b8cd9d0791a68009ab9 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 30 Jun 2026 21:57:13 -0700 Subject: [PATCH] fix(FN-7335): retry active merge pause aborts --- .changeset/merge-reviewing-pause-abort.md | 7 +++++++ .../merge-node-paused-abort-retryable.test.ts | 16 ++++++++++++++++ packages/engine/src/executor.ts | 10 +++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changeset/merge-reviewing-pause-abort.md diff --git a/.changeset/merge-reviewing-pause-abort.md b/.changeset/merge-reviewing-pause-abort.md new file mode 100644 index 0000000000..5ae1354869 --- /dev/null +++ b/.changeset/merge-reviewing-pause-abort.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Retry workflow merge-node pause aborts while merge review is active. +category: fix +dev: Treats transient in-review statuses such as reviewing/merging as safe for bounded merge retry. diff --git a/packages/engine/src/__tests__/reliability-interactions/merge-node-paused-abort-retryable.test.ts b/packages/engine/src/__tests__/reliability-interactions/merge-node-paused-abort-retryable.test.ts index ea2e05a0ec..9db6aa4df9 100644 --- a/packages/engine/src/__tests__/reliability-interactions/merge-node-paused-abort-retryable.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/merge-node-paused-abort-retryable.test.ts @@ -127,6 +127,22 @@ describe("merge-node paused-abort retry classification (FN-6735)", () => { expect(store.updateTask).not.toHaveBeenCalledWith(task.id, expect.objectContaining({ status: "failed" }), undefined); }); + it("retries FN-7335-style merge pause aborts while AI merge review status is active", async () => { + const { store, task, executor, mergeRequester } = makeHarness({ status: "reviewing" }); + + await invokeGraphFailure(executor, task, "merge"); + + expect(mergeRequester).toHaveBeenCalledWith(task.id); + const messages = logText(store); + expect(messages).toContain("Workflow graph merge failure at node 'merge' routed to bounded auto-merge retry after benign pause/resume abort"); + expect(messages).not.toContain("operator action required"); + expect(store.updateTask).not.toHaveBeenCalledWith( + task.id, + expect.objectContaining({ status: "failed" }), + undefined, + ); + }); + it("parks genuine merge conflicts as terminal instead of retrying forever", async () => { const { store, task, executor, mergeRequester } = makeHarness(); diff --git a/packages/engine/src/executor.ts b/packages/engine/src/executor.ts index a500f7ae19..58a0ce9181 100644 --- a/packages/engine/src/executor.ts +++ b/packages/engine/src/executor.ts @@ -7592,6 +7592,14 @@ export class TaskExecutor { || normalized.includes("max retries"); } + private isRetryableMergePauseAbortStatus(status: string | null | undefined): boolean { + /* + FNXC:WorkflowMerge 2026-07-01-22:05: + FN-7335 surfaced a merge-node pause/resume abort while the row was legitimately `in-review` with status="reviewing" from the AI merge reviewer. That status is merge activity, not a pre-existing terminal failure; keep the retry classifier strict on real errors while allowing transient merge/review statuses to re-enter bounded merge retry. + */ + return status == null || status === "reviewing" || status === "merging" || status === "merging-pr"; + } + private async isRetryableBenignMergePauseAbort( live: TaskDetail, result: WorkflowGraphTaskRunResult, @@ -7605,7 +7613,7 @@ export class TaskExecutor { if (!pausedAborted) return false; if (abortProvenance === "global-pause" || live.userPaused === true) return false; if (abortProvenance === "completion-finalize") return false; - if (live.column !== "in-review" || live.status != null || live.error != null) return false; + if (live.column !== "in-review" || !this.isRetryableMergePauseAbortStatus(live.status) || live.error != null) return false; if (live.mergeDetails?.mergeConfirmed === true) return false; if (this.isTerminalMergeGraphFailureValue(this.graphFailureValue(result))) return false; const failedNode = result.visitedNodeIds[result.visitedNodeIds.length - 1];