From cae7d088797284d45dfbc2adc0e12b362b1cff74 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Mon, 16 Mar 2026 11:57:54 -0600 Subject: [PATCH] fix: prevent infinite loop when milestone detection silently fails (#456) findMilestoneIds() had a bare catch that returned [] on any error, causing showSmartEntry() to think no milestones exist and restart the new-project discuss flow in a loop. Add error logging when the directory exists but scanning fails, and a sanity check that warns the user instead of looping when the directory has entries that weren't recognized. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/guided-flow.ts | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index 0f93c2550..dac219447 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -214,7 +214,11 @@ export function findMilestoneIds(basePath: string): string[] { // Apply custom queue order if available, else fall back to numeric sort const customOrder = loadQueueOrder(basePath); return sortByQueueOrder(ids, customOrder); - } catch { + } catch (err) { + // Log why milestone scanning failed — silent [] here causes infinite loops (#456) + if (existsSync(dir)) { + console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${err instanceof Error ? err.message : String(err)}`); + } return []; } } @@ -1002,6 +1006,27 @@ export async function showSmartEntry( } const milestoneIds = findMilestoneIds(basePath); + + // Sanity check (#456): if findMilestoneIds returns [] but the milestones + // directory has contents, something went wrong (permissions, stale worktree + // cwd, etc). Warn instead of silently starting a new-project flow. + if (milestoneIds.length === 0) { + const mDir = milestonesDir(basePath); + if (existsSync(mDir)) { + try { + const entries = readdirSync(mDir); + if (entries.length > 0) { + ctx.ui.notify( + `Milestone directory has ${entries.length} entries but none were recognized as milestones. ` + + `This may indicate a corrupted state or wrong working directory. Run \`/gsd doctor\` to diagnose.`, + "warning", + ); + return; + } + } catch { /* directory exists but unreadable — fall through to normal flow */ } + } + } + const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids; const nextId = nextMilestoneId(milestoneIds, uniqueMilestoneIds); const isFirst = milestoneIds.length === 0;