fix(FN-954): allow retry for stuck-killed tasks across all interfaces
- Extend retry API to accept 'stuck-killed' as a retryable status alongside 'failed' - Update CLI retry command to recognize stuck-killed tasks - Update pi extension retry tool to handle stuck-killed tasks - Show retry button in TaskDetailModal for stuck-killed task status - Add changeset for patch release
This commit is contained in:
@@ -1243,7 +1243,7 @@ export function TaskDetailModal({
|
||||
Refine
|
||||
</button>
|
||||
)}
|
||||
{task.status === "failed" && onRetryTask && (
|
||||
{(task.status === "failed" || task.status === "stuck-killed") && onRetryTask && (
|
||||
<button className="btn btn-warning btn-sm" onClick={handleRetry}>
|
||||
Retry
|
||||
</button>
|
||||
|
||||
@@ -704,7 +704,7 @@ describe("POST /tasks/:id/retry", () => {
|
||||
expect(store.moveTask).toHaveBeenCalledWith("KB-001", "todo");
|
||||
});
|
||||
|
||||
it("returns 400 when task is not in failed state", async () => {
|
||||
it("returns 400 when task is not in a retryable state", async () => {
|
||||
const activeTask = { ...FAKE_TASK_DETAIL, status: "executing" };
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue(activeTask);
|
||||
|
||||
@@ -713,7 +713,7 @@ describe("POST /tasks/:id/retry", () => {
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toContain("not in a failed state");
|
||||
expect(res.body.error).toContain("not in a retryable state");
|
||||
});
|
||||
|
||||
it("retries a failed task in any column (not just in-progress)", async () => {
|
||||
@@ -731,6 +731,23 @@ describe("POST /tasks/:id/retry", () => {
|
||||
expect(store.updateTask).toHaveBeenCalledWith("KB-001", { status: undefined, error: undefined });
|
||||
expect(store.moveTask).toHaveBeenCalledWith("KB-001", "todo");
|
||||
});
|
||||
|
||||
it("retries a stuck-killed task and moves it to todo", async () => {
|
||||
const stuckTask = { ...FAKE_TASK_DETAIL, status: "stuck-killed", column: "in-progress" };
|
||||
const movedTask = { ...FAKE_TASK_DETAIL, column: "todo", status: undefined };
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue(stuckTask);
|
||||
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue(stuckTask);
|
||||
(store.moveTask as ReturnType<typeof vi.fn>).mockResolvedValue(movedTask);
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/tasks/KB-001/retry", JSON.stringify({}), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("KB-001", { status: undefined, error: undefined });
|
||||
expect(store.moveTask).toHaveBeenCalledWith("KB-001", "todo");
|
||||
expect(store.logEntry).toHaveBeenCalledWith("KB-001", "Retry requested from dashboard");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /tasks/:id/duplicate", () => {
|
||||
|
||||
@@ -1784,13 +1784,13 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
}
|
||||
});
|
||||
|
||||
// Retry failed task
|
||||
// Retry failed or stuck-killed task
|
||||
router.post("/tasks/:id/retry", async (req, res) => {
|
||||
try {
|
||||
const scopedStore = await getScopedStore(req);
|
||||
const task = await scopedStore.getTask(req.params.id);
|
||||
if (task.status !== "failed") {
|
||||
res.status(400).json({ error: "Task is not in a failed state" });
|
||||
if (task.status !== "failed" && task.status !== "stuck-killed") {
|
||||
res.status(400).json({ error: `Task is not in a retryable state (current status: ${task.status || 'none'})` });
|
||||
return;
|
||||
}
|
||||
await scopedStore.updateTask(req.params.id, { status: undefined, error: undefined });
|
||||
|
||||
Reference in New Issue
Block a user