From 85960464ebdc94dda9283b24a92c847da564ddf9 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Sun, 15 Mar 2026 08:23:57 -0600 Subject: [PATCH] fix(auto): clear parse cache in dispatch + verify completion in handleAgentEnd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kills two independent failure paths causing the recurring dispatch loop bug: Path B: dispatchNextUnit() called clearPathCache() but not clearParseCache(), allowing stale parsed roadmap data (with [ ] instead of [x]) to persist through the doctor→dispatch transition. Path A: handleAgentEnd() never verified whether the just-completed unit produced its expected artifact before re-entering the dispatch loop. Now persists completion key after verification, so the idempotency check in dispatchNextUnit() skips already-completed units. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 9db275803..3abaf37fc 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -836,6 +836,24 @@ export async function handleAgentEnd( } catch { // Non-fatal } + + // ── Path A fix: verify artifact and persist completion before re-entering dispatch ── + // After doctor + rebuildState, check whether the just-completed unit actually + // produced its expected artifact. If so, persist the completion key now so the + // idempotency check at the top of dispatchNextUnit() skips it — even if + // deriveState() still returns this unit as active (e.g. branch mismatch). + try { + if (verifyExpectedArtifact(currentUnit.type, currentUnit.id, basePath)) { + const completionKey = `${currentUnit.type}/${currentUnit.id}`; + if (!completedKeySet.has(completionKey)) { + persistCompletedKey(basePath, completionKey); + completedKeySet.add(completionKey); + } + invalidateStateCache(); + } + } catch { + // Non-fatal — worst case we fall through to normal dispatch which has its own checks + } } // ── Post-unit hooks: check if a configured hook should run before normal dispatch ── @@ -1391,6 +1409,9 @@ async function dispatchNextUnit( // Clear stale directory listing cache so deriveState sees fresh disk state (#431) clearPathCache(); + // Clear parsed roadmap/plan cache — doctor may have re-populated it with + // stale data between handleAgentEnd and this dispatch call (Path B fix). + clearParseCache(); let state = await deriveState(basePath); let mid = state.activeMilestone?.id;