fix: defer stuck-kill requeue to executor finally block to prevent race

When the stuck task detector killed a task and immediately called
moveTask("todo"), the scheduler could re-dispatch the task before the
old execution's finally block cleared this.executing. The new execute()
call hit the guard and silently returned, stranding the task in
"in-progress" with no active session or worktree (seen on FN-810/FN-912).

Move the requeue responsibility from StuckTaskDetector.killAndRetry to
the executor's finally block, which runs after this.executing.delete().
The beforeRequeue budget check now runs before session.dispose() and its
result is passed via StuckTaskEvent.shouldRequeue → markStuckAborted().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-04 12:26:13 -07:00
parent 1ab1981557
commit fbda14717e
5 changed files with 144 additions and 65 deletions

View File

@@ -578,12 +578,12 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
const stuckTaskDetector = new StuckTaskDetector(store, {
beforeRequeue: (taskId) => selfHealing.checkStuckBudget(taskId),
onStuck: (event) => {
executorRef.current?.markStuckAborted(event.taskId);
executorRef.current?.markStuckAborted(event.taskId, event.shouldRequeue);
console.log(
`[engine] ⚠ ${event.taskId} stuck (${event.reason}) — ` +
`no progress for ${Math.round(event.noProgressMs / 60_000)}min, ` +
`${event.activitySinceProgress} events since last progress — ` +
`terminated, will retry`,
`terminated, ${event.shouldRequeue ? "will retry" : "budget exhausted"}`,
);
},
});