fix(engine): skip paused tasks in interrupted-merge recovery

recoverInterruptedMergingTasks was the one self-healing scan that didn't
guard !task.paused. A user who paused a task mid-merge (status=merging,
column=in-review) would still see the recovery scan finalize or unblock
the merge once the stuck timeout elapsed, violating their pause intent.

The other 12 self-healing recovery scans either explicitly skip paused
tasks or operate on terminal/metadata-only state where pause is moot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-28 16:59:55 -07:00
parent 053f6131b9
commit 105a4dfef6
2 changed files with 31 additions and 0 deletions

View File

@@ -1368,6 +1368,36 @@ describe("SelfHealingManager", () => {
managerWithRecovery.stop();
});
it("does not recover paused merging tasks even when past the stuck timeout", async () => {
const managerWithRecovery = new SelfHealingManager(store, {
rootDir: "/tmp/test-project",
});
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
taskStuckTimeoutMs: 60_000,
});
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
{
id: "FN-1677",
column: "in-review",
status: "merging",
paused: true,
error: null,
updatedAt: new Date(Date.now() - 24 * 60 * 60_000).toISOString(),
steps: [{ name: "Ship it", status: "done" }],
log: [],
},
]);
const result = await managerWithRecovery.recoverInterruptedMergingTasks();
expect(result).toBe(0);
expect(store.updateTask).not.toHaveBeenCalled();
expect(store.moveTask).not.toHaveBeenCalled();
managerWithRecovery.stop();
});
it("does not recover stale merging tasks when stuck detection is disabled", async () => {
const managerWithRecovery = new SelfHealingManager(store, {
rootDir: "/tmp/test-project",

View File

@@ -927,6 +927,7 @@ export class SelfHealingManager {
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
const candidates = tasks.filter((task) =>
task.column === "in-review" &&
!task.paused &&
Boolean(task.status && ACTIVE_MERGE_STATUSES.has(task.status)) &&
this.isPastInterruptedMergeGrace(task, timeoutMs),
);