From c75f8ef30f51709ec2a42543864475b302ee7c65 Mon Sep 17 00:00:00 2001 From: Flux Labs Date: Sun, 15 Mar 2026 18:04:36 -0500 Subject: [PATCH] fix(auto): stop auto-mode when dispatch gap watchdog fails to dispatch (#537) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/resources/extensions/gsd/auto.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 962e7a9ab..23b375262 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -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); }