fix(auto): stop auto-mode when dispatch gap watchdog fails to dispatch (#537)

The dispatch gap watchdog is a one-shot timer that fires 5s after a unit
completes without a follow-up dispatch. Previously, if the watchdog's
dispatchNextUnit() call returned without actually dispatching a unit
(no sendMessage called), auto-mode was left permanently active but idle
— no new watchdog was started and no stopAuto was called.

This happened when:
- State between milestones had no dispatchable unit
- Stale completed-units.json after GSD updates caused skip loops
- dispatchNextUnit silently returned without finding work

Now the watchdog checks whether a unit was actually dispatched after its
retry attempt. If not, it stops auto-mode cleanly with a user-facing
message instead of leaving it stuck.

Closes #537
This commit is contained in:
Flux Labs 2026-03-15 18:04:36 -05:00
parent a43836ffbb
commit c75f8ef30f

View file

@ -325,6 +325,18 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
"error",
);
await stopAuto(ctx, pi);
return;
}
// If dispatchNextUnit returned normally but still didn't dispatch a unit
// (no sendMessage called → no timeout set), auto-mode is permanently
// stalled. Stop cleanly instead of leaving it active but idle (#537).
if (active && !unitTimeoutHandle && !wrapupWarningHandle) {
ctx.ui.notify(
"Auto-mode stalled — no dispatchable unit found after retry. Stopping. Run /gsd auto to restart.",
"warning",
);
await stopAuto(ctx, pi);
}
}, DISPATCH_GAP_TIMEOUT_MS);
}