fix(engine): resolve stuck-loop requeue review feedback

This commit is contained in:
Phil Larson
2026-05-31 10:50:58 -07:00
parent 5861884456
commit 5a163ce07a
2 changed files with 30 additions and 32 deletions

View File

@@ -440,14 +440,11 @@ describe("SelfHealingManager", () => {
const result = await manager.checkStuckBudget("FN-001", "loop");
expect(result).toBe(false);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", expect.objectContaining({
stuckKillCount: 7,
paused: false,
userPaused: false,
pausedReason: null,
status: "queued",
}));
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo", { preserveProgress: true });
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { stuckKillCount: 7 });
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo", {
preserveProgress: true,
preserveStatus: true,
});
expect(store.updateTask).toHaveBeenLastCalledWith("FN-001", expect.objectContaining({
stuckKillCount: 7,
paused: false,
@@ -462,7 +459,7 @@ describe("SelfHealingManager", () => {
);
});
it("leaves incomplete stuck-loop exhaustion unpaused when todo parking fails", async () => {
it("falls back to executor requeue when todo parking fails", async () => {
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({
id: "FN-001",
column: "in-progress",
@@ -478,19 +475,22 @@ describe("SelfHealingManager", () => {
const result = await manager.checkStuckBudget("FN-001", "loop");
expect(result).toBe(false);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", expect.objectContaining({
stuckKillCount: 7,
expect(result).toBe(true);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { stuckKillCount: 7 });
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo", {
preserveProgress: true,
preserveStatus: true,
});
expect(store.updateTask).not.toHaveBeenCalledWith("FN-001", expect.objectContaining({
paused: false,
userPaused: false,
pausedReason: null,
status: "queued",
}));
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo", { preserveProgress: true });
expect(store.handoffToReview).not.toHaveBeenCalled();
expect(store.logEntry).toHaveBeenCalledWith(
"FN-001",
"STUCK_LOOP_EXHAUSTED: incomplete task exhausted stuck kill budget (7/6), last reason=loop. Failed to move task to todo (database is busy); task remains unpaused for scheduler retry.",
"STUCK_LOOP_EXHAUSTED: incomplete task exhausted stuck kill budget (7/6), last reason=loop. Failed to move task to todo (database is busy); falling back to executor stuck-kill requeue.",
);
});

View File

@@ -1073,34 +1073,32 @@ export class SelfHealingManager {
if (hasIncompleteSteps) {
log.warn(`${taskId} exceeded stuck kill budget (${newCount}/${maxKills}, reason=${reason}) with incomplete steps — re-queueing in todo with progress preserved`);
await this.store.updateTask(taskId, {
stuckKillCount: newCount,
paused: false,
userPaused: false,
pausedReason: null,
status: "queued",
} as unknown as Partial<Task> & { userPaused: boolean });
let parkedInTodo = true;
let moveErrMessage = "";
await this.store.updateTask(taskId, { stuckKillCount: newCount });
try {
await this.store.moveTask(taskId, "todo", { preserveProgress: true });
await this.store.updateTask(taskId, {
await this.store.moveTask(taskId, "todo", {
preserveProgress: true,
preserveStatus: true,
});
const requeueUpdate = {
stuckKillCount: newCount,
paused: false,
userPaused: false,
pausedReason: null,
status: "queued",
} as unknown as Partial<Task> & { userPaused: boolean });
} satisfies Parameters<typeof this.store.updateTask>[1] & { userPaused: boolean };
await this.store.updateTask(taskId, requeueUpdate);
} catch (moveErr: unknown) {
parkedInTodo = false;
moveErrMessage = moveErr instanceof Error ? moveErr.message : String(moveErr);
log.warn(`${taskId} moveTask(todo) failed (${moveErrMessage}) after incomplete STUCK_LOOP_EXHAUSTED terminalization — task remains queued for scheduler retry`);
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`);
await this.store.logEntry(
taskId,
`STUCK_LOOP_EXHAUSTED: incomplete task exhausted stuck kill budget (${newCount}/${maxKills}), last reason=${reason}. Failed to move task to todo (${moveErrMessage}); falling back to executor stuck-kill requeue.`,
);
return true;
}
await this.store.logEntry(
taskId,
parkedInTodo
? `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.`
: `STUCK_LOOP_EXHAUSTED: incomplete task exhausted stuck kill budget (${newCount}/${maxKills}), last reason=${reason}. Failed to move task to todo (${moveErrMessage}); task remains unpaused for scheduler retry.`,
`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.`,
);
return false;
}