Clear stale failed status on benign todo pause-abort
A pause/resume abort parked status:"failed" on an earlier non-todo observation stays dispatchable (scheduler filters on column+paused, not status, scheduler.ts:1288) and re-enters the FN-6782 benign-todo branch. That branch logged "benign" but left status:"failed"/error on the row, so the board kept showing it failed and the deferred failure notification fired (notification-service fire-time check sees status==="failed") — contradicting the benign log. recoverPausedAbortFailures that would clear it is suppressed during global/engine pause (self-healing.ts:8125), so the failure survived the pause window. Reconcile the row with the benign reclassification: clear status/error when the live row carries them, so the board agrees it's benign and the pending notification is suppressed at dispatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Clear the stale `failed` status when a pause/resume abort is reclassified as a benign todo re-queue, so the task no longer surfaces as failed on the board and the deferred failure notification is suppressed. Previously a pause-abort parked `status:"failed"` on an earlier non-todo observation stayed dispatchable (the scheduler filters on column+paused, not status), re-entered the benign-todo branch, and was logged benign while the row stayed failed — firing a contradictory failure alert during global pause when self-healing recovery was suppressed.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import "./executor-test-helpers.js";
|
||||
import { TaskExecutor } from "../executor.js";
|
||||
import { createMockStore, resetExecutorMocks } from "./executor-test-helpers.js";
|
||||
@@ -84,6 +84,36 @@ describe("pause-abort benign requeue-to-todo (FN-6782)", () => {
|
||||
expect((executor as any).activeWorktrees.has(task.id)).toBe(false);
|
||||
});
|
||||
|
||||
it("clears a stale failed status when reclassifying a todo pause-abort as benign (no lingering failure notification)", async () => {
|
||||
// FNXC:WorkflowLifecycle a pause-abort parked status:"failed" on an earlier
|
||||
// non-todo observation stays dispatchable (scheduler filters column+paused,
|
||||
// not status) and re-enters this branch in todo. The benign reclassification
|
||||
// must reconcile the row to status:null/error:null — otherwise the persisted
|
||||
// failure survives, the board shows it failed, and the deferred failure
|
||||
// notification fires despite the benign log.
|
||||
const { store, task, executor } = makeHarness({
|
||||
column: "todo",
|
||||
status: "failed",
|
||||
error: "Workflow graph failure surfaced after paused engine abort during pause/resume",
|
||||
});
|
||||
(executor as any).activeWorktrees.set(task.id, task.worktree);
|
||||
|
||||
await invokeGraphFailure(executor, task);
|
||||
|
||||
const clearedFailure = store.updateTask.mock.calls.some(
|
||||
(call: unknown[]) => {
|
||||
const patch = call[1] as { status?: unknown; error?: unknown } | undefined;
|
||||
return patch?.status === null && patch?.error === null;
|
||||
},
|
||||
);
|
||||
expect(clearedFailure).toBe(true);
|
||||
const reParkedFailed = store.updateTask.mock.calls.some(
|
||||
(call: unknown[]) => (call[1] as { status?: string } | undefined)?.status === "failed",
|
||||
);
|
||||
expect(reParkedFailed).toBe(false);
|
||||
expect(logText(store)).toContain("benign, cleared for normal scheduling");
|
||||
});
|
||||
|
||||
it("STILL parks a non-todo (in-review) pause-abort as operator-action failed", async () => {
|
||||
const { store, task, executor } = makeHarness({ column: "in-review" });
|
||||
|
||||
|
||||
@@ -6701,6 +6701,20 @@ export class TaskExecutor {
|
||||
const todoBenign = `Workflow graph run ended during ${pauseProvenance} with task re-queued to todo — benign, cleared for normal scheduling`;
|
||||
executorLog.log(`${task.id}: ${todoBenign}`);
|
||||
await this.store.logEntry(task.id, todoBenign, undefined, this.getRunContextFor(task.id));
|
||||
// FNXC:WorkflowLifecycle 2026-06-20: reconcile a stale persisted
|
||||
// failure with the benign reclassification. A pause-abort parked
|
||||
// `status:"failed"` on an earlier non-todo observation stays
|
||||
// dispatchable (scheduler.ts filters column+paused, NOT status) and
|
||||
// re-enters this branch in `todo`; `recoverPausedAbortFailures` that
|
||||
// would clear it is suppressed during global/engine pause
|
||||
// (self-healing.ts). Leaving the row failed contradicts the benign
|
||||
// log: the board shows it failed AND the deferred failure
|
||||
// notification fires (notification-service fire-time check sees
|
||||
// status === "failed"). Clear status/error here so the row matches
|
||||
// the log and the pending notification is suppressed at dispatch.
|
||||
if (live.status != null || live.error != null) {
|
||||
await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
|
||||
}
|
||||
await this.persistTokenUsage(task.id);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user