#6481·gsd-2

Auto-mode stuck loop: complete-slice re-dispatches with no attempt cap when LLM silently refuses to write SUMMARY

Author: waja1n0zCreated Jul 11, 2026Updated Jul 11, 2026

Problem

Auto-mode gets stuck in summarizing → complete-slice when the dispatched LLM finishes the unit cleanly (0 errors, ~13 tool calls) but silently refuses to write the slice SUMMARY.md/UAT.md — typically reasoning "Slice needs execution follow-up." — without calling gsd_slice_complete, gsd_task_reopen, or gsd_slice_replan. The state machine has no exit: verify-fail fires, the same rule re-dispatches, and the loop only ends via the generic stuck-loop detector + auto-exit.

Observed on M031/S02: three re-dispatches of complete-slice/M031/S02 in a row (seq 601–602 + retries), each producing zero SUMMARY on disk while all five task SUMMARYs are present and DB has all tasks complete.

Root Cause

auto-dispatch.ts — the summarizing → complete-slice rule unconditionally re-dispatches whenever state.phase === "summarizing". No attempt counter, no divergence signal, no fallback like run-uat's MAX_UAT_ATTEMPTS.

typescript
// auto-dispatch.ts (summarizing → complete-slice rule)
{
    name: "summarizing → complete-slice",
    match: async ({ state, mid, midTitle, basePath }) => {
        if (state.phase !== "summarizing") return null;
        if (!state.activeSlice) return missingSliceStop(mid, state.phase);
        const sid = state.activeSlice.id;
        // No attempt counter, no divergence check.
        return {
            action: "dispatch",
            unitType: "complete-slice",
            unitId: `${mid}/${sid}`,
            prompt: await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, basePath),
        };
    },
},

auto-recovery.ts (verifyUnitCompletion) only checks DB slice.status === "complete" and disk SUMMARY/UAT. It has no way to distinguish "unit crashed" from "unit finished cleanly but LLM chose not to act" — both look like existsSync false, so the dispatcher happily goes around again.

Expected Behavior

Two-part fix:

  1. Cap complete-slice re-dispatch attempts analogous to run-uat's MAX_UAT_ATTEMPTS. After N (suggested: 2) attempts where the previous unit ended with errors === 0, toolCalls > 0, and no SUMMARY on disk and no reopen/replan call, escalate: write S##-BLOCKER.md containing the LLM's last reasoning, transition to blocked, and stop the loop for human/planner review.
  2. Divergence signal in buildCompleteSlicePrompt: when metrics.json already has a prior complete-slice/<mid>/<sid> entry with cost > 0, errors === 0, and SUMMARY still absent, inject a mandatory-action block into the prompt that forces exactly one of gsd_slice_complete, gsd_task_reopen, gsd_slice_replan, or write S##-BLOCKER.md. Silent refusal must not be a valid outcome.

Environment

  • GSD version: 3.0.0
  • Model: claude-opus-4-7
  • Unit: complete-slice/M031/S02

Reproduction Context

  • Phase: summarizing (all 5 tasks in slice have status=complete in DB and T0N-SUMMARY.md on disk)
  • Trigger: LLM dispatched for complete-slice completes 13 tool calls with 0 errors, reasons "Slice S02 needs execution follow-up.", never writes S02-SUMMARY.md or S02-UAT.md, never calls gsd_task_reopen
  • Result: verifyUnitCompletion reports existsSync false for S02-SUMMARY.md; dispatcher matches summarizing → complete-slice again; same LLM behavior; loop until stuck-loop detector

Forensic Evidence

  • 3 dispatches of complete-slice/M031/S02 within 20 attempts
  • Verify-fail warnings: existsSync false for .../S02/S02-SUMMARY.md (x3) and .../S02/tasks/T05-SUMMARY.md (x2 — resolved in prior attempt)
  • Metrics: successful attempt cost $1.30 / 1m33s / 13 tool calls / 0 errors — no SUMMARY produced
  • Recent journal (2026-07-11T10:53–10:56Z): three dispatch-match rule=summarizing → complete-slice unit=M031/S02 events, each followed within 3s by auto-exit
  • Related but distinct: prior issue on complete-slice reopening tasks for inherited verification failure — this new symptom is complete-slice refusing to write SUMMARY entirely, so the state machine has no exit at all

Auto-generated by /gsd forensics