fix: break needs-discussion infinite loop when survivor branch exists (#1726) (#1778)

When a milestone has only CONTEXT-DRAFT.md, the survivor branch check
sets hasSurvivorBranch=true and skips all showSmartEntry calls. Auto-mode
then dispatches needs-discussion->stop, creating an infinite loop on
every /gsd run. Add a pre-check: when hasSurvivorBranch is true AND
phase is needs-discussion, route to the interactive discussion handler.

Closes #1726

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-21 09:46:17 -06:00 committed by GitHub
parent a95d420972
commit dda01fa648
2 changed files with 57 additions and 0 deletions

View file

@ -315,6 +315,32 @@ export async function bootstrapAutoSession(
}
}
// Survivor branch exists but milestone still needs discussion (#1726):
// The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md.
// Route to the interactive discussion handler instead of falling through to
// auto-mode, which would immediately stop with "needs discussion".
if (hasSurvivorBranch && state.phase === "needs-discussion") {
const { showSmartEntry } = await import("./guided-flow.js");
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
invalidateAllCaches();
const postState = await deriveState(base);
if (
postState.activeMilestone &&
postState.phase !== "needs-discussion"
) {
state = postState;
// Discussion succeeded — clear survivor flag so normal flow continues
hasSurvivorBranch = false;
} else {
ctx.ui.notify(
"Discussion completed but milestone draft was not promoted. Run /gsd to try again.",
"warning",
);
return releaseLockAndReturn();
}
}
if (!hasSurvivorBranch) {
// No active work — start a new milestone via discuss flow
if (!state.activeMilestone || state.phase === "complete") {

View file

@ -200,6 +200,37 @@ async function main(): Promise<void> {
}
}
// ─── 7. Survivor branch + needs-discussion routes to showSmartEntry (#1726) ─
console.log("\n=== 7. Survivor branch + needs-discussion routes to showSmartEntry ===");
{
const source = readAutoStartSource();
// When hasSurvivorBranch is true AND phase is needs-discussion, the code
// must route to showSmartEntry instead of falling through to auto-mode.
const survivorNeedsDiscussion = source.match(
/if\s*\(hasSurvivorBranch\s*&&\s*state\.phase\s*===\s*"needs-discussion"\)\s*\{[^}]*showSmartEntry/s,
);
assertTrue(!!survivorNeedsDiscussion,
"hasSurvivorBranch && needs-discussion must route to showSmartEntry");
// Verify the handler checks if the discussion succeeded
const handlerBlock = source.match(
/if\s*\(hasSurvivorBranch\s*&&\s*state\.phase\s*===\s*"needs-discussion"\)\s*\{([\s\S]*?)\n \}/,
);
assertTrue(!!handlerBlock,
"found survivor + needs-discussion handler block");
if (handlerBlock) {
assertTrue(
handlerBlock[1].includes('postState.phase !== "needs-discussion"'),
"handler must check if phase advanced after discussion",
);
assertTrue(
handlerBlock[1].includes("releaseLockAndReturn"),
"handler must abort if discussion didn't promote draft",
);
}
}
report();
}