From e6bbd035ba95797e0339244a6587bae31c7b7bb8 Mon Sep 17 00:00:00 2001 From: Juan Francisco Lebrero <101231690+frizynn@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:31:15 -0300 Subject: [PATCH] fix: auto-discard bootstrap crash locks and clean auto.lock on exit (#1397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two root causes for the false "Interrupted Session Detected" prompt that appears every time /gsd is run after a normal exit: 1. guided-flow.ts showed the crash recovery menu even for bootstrap crashes (unitType="starting", unitId="bootstrap", completedUnits=0) where no work was lost. Now these are silently discarded — the menu only appears when real auto-mode work was interrupted. 2. session-lock.ts exit handler cleaned the OS lock directory (.gsd.lock/) but not the auto.lock metadata file. On next startup, readCrashLock() found the stale file and triggered false recovery. Now the exit handler also removes auto.lock. --- src/resources/extensions/gsd/guided-flow.ts | 33 +++++++++++++------- src/resources/extensions/gsd/session-lock.ts | 6 ++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index 62fcd0d5e..750906401 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -792,17 +792,28 @@ export async function showSmartEntry( const crashLock = readCrashLock(basePath); if (crashLock) { clearLock(basePath); - const resume = await showNextAction(ctx, { - title: "GSD — Interrupted Session Detected", - summary: [formatCrashInfo(crashLock)], - actions: [ - { id: "resume", label: "Resume with /gsd auto", description: "Pick up where it left off", recommended: true }, - { id: "continue", label: "Continue manually", description: "Open the wizard as normal" }, - ], - }); - if (resume === "resume") { - await startAuto(ctx, pi, basePath, false); - return; + + // Bootstrap crash with zero completed units = no work was lost. + // Auto-discard instead of prompting the user — this commonly happens + // when the user exits during init wizard or discuss phase before any + // real auto-mode work begins. + const isBootstrapCrash = crashLock.unitType === "starting" + && crashLock.unitId === "bootstrap" + && crashLock.completedUnits === 0; + + if (!isBootstrapCrash) { + const resume = await showNextAction(ctx, { + title: "GSD — Interrupted Session Detected", + summary: [formatCrashInfo(crashLock)], + actions: [ + { id: "resume", label: "Resume with /gsd auto", description: "Pick up where it left off", recommended: true }, + { id: "continue", label: "Continue manually", description: "Open the wizard as normal" }, + ], + }); + if (resume === "resume") { + await startAuto(ctx, pi, basePath, false); + return; + } } } diff --git a/src/resources/extensions/gsd/session-lock.ts b/src/resources/extensions/gsd/session-lock.ts index 1541d9172..0f0ca227b 100644 --- a/src/resources/extensions/gsd/session-lock.ts +++ b/src/resources/extensions/gsd/session-lock.ts @@ -122,6 +122,12 @@ function ensureExitHandler(gsdDir: string): void { try { if (_releaseFunction) { _releaseFunction(); _releaseFunction = null; } } catch { /* best-effort */ } + // Remove the auto.lock metadata file so crash-recovery doesn't + // falsely detect an interrupted session on the next startup. + try { + const lockFile = join(gsdDir, LOCK_FILE); + if (existsSync(lockFile)) unlinkSync(lockFile); + } catch { /* best-effort */ } try { const lockDir = join(gsdDir + ".lock"); if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });