fix(engine): split stuck requeue fallback handling
This commit is contained in:
@@ -4,6 +4,7 @@ import "../executor-test-helpers.js";
|
||||
import type { Task, TaskStore } from "@fusion/core";
|
||||
import { TaskExecutor } from "../../executor.js";
|
||||
import { SelfHealingManager } from "../../self-healing.js";
|
||||
import { isRunnableQueuedOverlapCandidate } from "../../scheduler.js";
|
||||
import { StuckTaskDetector } from "../../stuck-task-detector.js";
|
||||
|
||||
type MockTaskStore = TaskStore & EventEmitter & {
|
||||
@@ -219,6 +220,7 @@ describe("reliability interactions: non-progress churn", () => {
|
||||
expect(task.steps).toEqual([{ name: "Implement", status: "in-progress" }]);
|
||||
expect(task.log?.some((entry) => entry.action.includes("incomplete task exhausted stuck kill budget"))).toBe(true);
|
||||
expect(store.handoffToReview).not.toHaveBeenCalled();
|
||||
expect(isRunnableQueuedOverlapCandidate(task, [task])).toBe(true);
|
||||
|
||||
manager.stop();
|
||||
});
|
||||
|
||||
@@ -494,6 +494,40 @@ describe("SelfHealingManager", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("logs post-move requeue patch failures without executor fallback", async () => {
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
id: "FN-001",
|
||||
column: "in-progress",
|
||||
stuckKillCount: 6,
|
||||
steps: [
|
||||
{ name: "Preflight", status: "done" },
|
||||
{ name: "Delivery", status: "in-progress" },
|
||||
],
|
||||
} as unknown as Task);
|
||||
(store.updateTask as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({} as Task)
|
||||
.mockRejectedValueOnce(new Error("write conflict"));
|
||||
|
||||
manager.start();
|
||||
|
||||
const result = await manager.checkStuckBudget("FN-001", "loop");
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo", {
|
||||
preserveProgress: true,
|
||||
preserveStatus: true,
|
||||
});
|
||||
expect(store.logEntry).toHaveBeenCalledWith(
|
||||
"FN-001",
|
||||
"STUCK_LOOP_EXHAUSTED: incomplete task moved to todo with progress preserved, but post-move requeue patch failed (write conflict); scheduler retry may wait for the next state repair pass.",
|
||||
);
|
||||
expect(store.logEntry).toHaveBeenCalledWith(
|
||||
"FN-001",
|
||||
"STUCK_LOOP_EXHAUSTED: incomplete task exhausted stuck kill budget (7/6), last reason=loop. Re-queued in todo with progress preserved; scheduler may retry without manual unpause.",
|
||||
);
|
||||
expect(store.handoffToReview).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("terminalizes no-progress churn without incrementing stuck kill budget", async () => {
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
id: "FN-001",
|
||||
|
||||
@@ -1079,14 +1079,6 @@ export class SelfHealingManager {
|
||||
preserveProgress: true,
|
||||
preserveStatus: true,
|
||||
});
|
||||
const requeueUpdate = {
|
||||
stuckKillCount: newCount,
|
||||
paused: false,
|
||||
userPaused: false,
|
||||
pausedReason: null,
|
||||
status: "queued",
|
||||
} satisfies Parameters<typeof this.store.updateTask>[1] & { userPaused: boolean };
|
||||
await this.store.updateTask(taskId, requeueUpdate);
|
||||
} catch (moveErr: unknown) {
|
||||
const moveErrMessage = moveErr instanceof Error ? moveErr.message : String(moveErr);
|
||||
log.warn(`${taskId} moveTask(todo) failed (${moveErrMessage}) after incomplete STUCK_LOOP_EXHAUSTED terminalization — falling back to executor stuck-kill requeue`);
|
||||
@@ -1096,6 +1088,24 @@ export class SelfHealingManager {
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const requeueUpdate = {
|
||||
stuckKillCount: newCount,
|
||||
paused: false,
|
||||
userPaused: false,
|
||||
pausedReason: null,
|
||||
status: "queued",
|
||||
} satisfies Parameters<typeof this.store.updateTask>[1] & { userPaused: boolean };
|
||||
try {
|
||||
await this.store.updateTask(taskId, requeueUpdate);
|
||||
} catch (patchErr: unknown) {
|
||||
const patchErrMessage = patchErr instanceof Error ? patchErr.message : String(patchErr);
|
||||
log.warn(`${taskId} post-move requeue patch failed after incomplete STUCK_LOOP_EXHAUSTED terminalization: ${patchErrMessage}`);
|
||||
await this.store.logEntry(
|
||||
taskId,
|
||||
`STUCK_LOOP_EXHAUSTED: incomplete task moved to todo with progress preserved, but post-move requeue patch failed (${patchErrMessage}); scheduler retry may wait for the next state repair pass.`,
|
||||
);
|
||||
}
|
||||
await this.store.logEntry(
|
||||
taskId,
|
||||
`STUCK_LOOP_EXHAUSTED: incomplete task exhausted stuck kill budget (${newCount}/${maxKills}), last reason=${reason}. Re-queued in todo with progress preserved; scheduler may retry without manual unpause.`,
|
||||
|
||||
Reference in New Issue
Block a user