fix(FN-000): clear stale queued state on task resume

Resume paths (unpause, drift recovery, engine restart) bypassed the
scheduler's todo->in-progress clear, leaving actively executing tasks
labeled status="queued" with a lingering blockedBy. Broadened
clearResumeFailureState to null both fields alongside the existing
failure cleanup, and added a defensive UI backstop so the "Queued"
badge no longer renders for tasks in the in-progress column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-20 11:28:56 -07:00
parent ee55075440
commit ca6ed92dd1
3 changed files with 26 additions and 4 deletions

View File

@@ -1062,7 +1062,7 @@ function TaskCardComponent({
<Layers size={12} style={{ verticalAlign: "middle" }} /> {task.blockedBy}
</span>
)}
{(queued || task.status === "queued") && <span className="queued-badge"><Clock size={12} style={{ verticalAlign: "middle" }} /> Queued</span>}
{(queued || task.status === "queued") && task.column !== "in-progress" && <span className="queued-badge"><Clock size={12} style={{ verticalAlign: "middle" }} /> Queued</span>}
</div>
)}
<PluginSlot slotId="task-card-badge" projectId={projectId} />

View File

@@ -568,8 +568,8 @@ describe("TaskCard queued badge logic", () => {
}
/** Mirrors the queued-badge visibility condition from TaskCard.tsx */
function shouldShowQueuedBadge(opts: { queued?: boolean; status?: string | null }): boolean {
return !!(opts.queued || opts.status === "queued");
function shouldShowQueuedBadge(opts: { queued?: boolean; status?: string | null; column?: string }): boolean {
return !!(opts.queued || opts.status === "queued") && opts.column !== "in-progress";
}
it("shows queued-badge when queued prop is true", () => {
@@ -589,6 +589,11 @@ describe("TaskCard queued badge logic", () => {
expect(shouldShowQueuedBadge({})).toBe(false);
});
it("does NOT show queued-badge when column is 'in-progress' even if status is stale 'queued'", () => {
expect(shouldShowQueuedBadge({ status: "queued", column: "in-progress" })).toBe(false);
expect(shouldShowQueuedBadge({ queued: true, column: "in-progress" })).toBe(false);
});
it("does NOT show card-status-badge when status is 'queued'", () => {
expect(shouldShowStatusBadge("queued")).toBe(false);
});

View File

@@ -745,8 +745,25 @@ export class TaskExecutor {
}
private async clearResumeFailureState(task: Task): Promise<void> {
const updates: { status?: null; error?: null; blockedBy?: null } = {};
if (task.status === "failed" || task.error) {
await this.store.updateTask(task.id, { status: null, error: null });
updates.status = null;
updates.error = null;
}
// Pre-dispatch gating state must not survive into a resumed in-progress run.
// The scheduler sets status="queued" + blockedBy on dep/file-scope conflicts
// (scheduler.ts:618, 660) and clears them on the todo→in-progress transition
// (scheduler.ts:696). Resume paths (unpause, drift recovery, engine restart)
// bypass that clear, so a task can end up actively executing while still
// labeled "queued" in the UI.
if (task.status === "queued") {
updates.status = null;
}
if (task.blockedBy) {
updates.blockedBy = null;
}
if (Object.keys(updates).length > 0) {
await this.store.updateTask(task.id, updates);
}
}