fix: auto-discard bootstrap crash locks and clean auto.lock on exit (#1397)

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.
This commit is contained in:
Juan Francisco Lebrero 2026-03-19 11:31:15 -03:00 committed by GitHub
parent 2dc804a485
commit e6bbd035ba
2 changed files with 28 additions and 11 deletions

View file

@ -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;
}
}
}

View file

@ -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 });