fix(engine): paused-task guard for merged + misclassified review recovery
Two recovery scans operated on in-review tasks without checking !task.paused: - recoverMergedReviewTasks would move a paused task whose merge was already confirmed to done, against user intent. - recoverMisclassifiedFailures would clear the error on a paused failed task, defeating the user's intent to investigate manually. Add the paused guard to both, matching the pattern used by all other recovery scans. Completes the pause-vs-stuck audit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1024,6 +1024,31 @@ describe("SelfHealingManager", () => {
|
||||
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
it("does not clear errors on paused tasks (respects user investigate intent)", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, {
|
||||
rootDir: "/tmp/test-project",
|
||||
});
|
||||
|
||||
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
|
||||
{
|
||||
id: "FN-303",
|
||||
column: "in-review",
|
||||
status: "failed",
|
||||
paused: true,
|
||||
error: "Agent finished without calling task_done",
|
||||
steps: [{ status: "done" }, { status: "done" }],
|
||||
log: [],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await managerWithRecovery.recoverMisclassifiedFailures();
|
||||
|
||||
expect(result).toBe(0);
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("recoverPartialProgressNoTaskDoneFailures", () => {
|
||||
@@ -1642,6 +1667,33 @@ describe("SelfHealingManager", () => {
|
||||
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
it("does not move paused merged tasks to done (respects user pause intent)", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, {
|
||||
rootDir: "/tmp/test-project",
|
||||
});
|
||||
|
||||
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
|
||||
{
|
||||
id: "FN-352",
|
||||
column: "in-review",
|
||||
paused: true,
|
||||
mergeDetails: {
|
||||
mergeConfirmed: true,
|
||||
mergedAt: "2026-01-01T00:00:00.000Z",
|
||||
},
|
||||
log: [],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await managerWithRecovery.recoverMergedReviewTasks();
|
||||
|
||||
expect(result).toBe(0);
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("recoverReviewTasksWithFailedPreMergeSteps", () => {
|
||||
|
||||
@@ -1016,6 +1016,7 @@ export class SelfHealingManager {
|
||||
|
||||
const mergedButNotDone = tasks.filter((t) =>
|
||||
t.column === "in-review" &&
|
||||
!t.paused &&
|
||||
t.mergeDetails?.mergeConfirmed === true,
|
||||
);
|
||||
|
||||
@@ -1069,6 +1070,7 @@ export class SelfHealingManager {
|
||||
|
||||
const misclassified = tasks.filter((t) =>
|
||||
t.column === "in-review" &&
|
||||
!t.paused &&
|
||||
t.status === "failed" &&
|
||||
t.error?.includes("without calling task_done") &&
|
||||
t.steps.length > 0 &&
|
||||
|
||||
Reference in New Issue
Block a user