From 9992d24649e29d5a02b46eccc60344902ffa7ce8 Mon Sep 17 00:00:00 2001 From: Derek Pearson Date: Sat, 21 Mar 2026 14:03:18 -0400 Subject: [PATCH] fix(gsd): preserve interrupted-session resume mode Discard stale paused-session metadata on direct auto starts and pass paused step-mode through guided resume so the selected interrupted-session action matches the actual resumed mode. --- src/resources/extensions/gsd/auto.ts | 40 +++++++++++++------ .../tests/interrupted-session-auto.test.ts | 3 +- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index c55ccc46e..34c4b55dd 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -954,24 +954,40 @@ export async function startAuto( if (!s.paused) { try { const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base); + const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json"); if (meta?.milestoneId) { - s.currentMilestoneId = meta.milestoneId; - s.originalBasePath = meta.originalBasePath || base; - s.stepMode = meta.stepMode ?? requestedStepMode; - s.pausedSessionFile = meta.sessionFile ?? null; - s.paused = true; - const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json"); - try { unlinkSync(pausedPath); } catch { /* non-fatal */ } - ctx.ui.notify( - `Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`, - "info", - ); + const shouldResumePausedSession = + freshStartAssessment.classification === "recoverable" + && ( + freshStartAssessment.hasResumableDiskState + || !!freshStartAssessment.recoveryPrompt + || !!freshStartAssessment.lock + ); + if (shouldResumePausedSession) { + s.currentMilestoneId = meta.milestoneId; + s.originalBasePath = meta.originalBasePath || base; + s.stepMode = meta.stepMode ?? requestedStepMode; + s.pausedSessionFile = meta.sessionFile ?? null; + s.paused = true; + try { unlinkSync(pausedPath); } catch { /* non-fatal */ } + ctx.ui.notify( + `Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`, + "info", + ); + } else if (existsSync(pausedPath)) { + try { unlinkSync(pausedPath); } catch { /* non-fatal */ } + } } } catch { // Malformed or missing — proceed with fresh bootstrap } } + if (!s.paused) { + s.stepMode = requestedStepMode; + } + + if (freshStartAssessment.classification !== "running" && freshStartAssessment.lock) { clearLock(base); } @@ -1005,7 +1021,7 @@ export async function startAuto( s.paused = false; s.active = true; s.verbose = verboseMode; - s.stepMode = requestedStepMode; + s.stepMode = s.stepMode || requestedStepMode; s.cmdCtx = ctx; s.basePath = base; s.unitDispatchCount.clear(); diff --git a/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts b/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts index f81cb6266..fefa690a5 100644 --- a/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +++ b/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts @@ -132,11 +132,12 @@ test("direct /gsd auto source only resumes paused-session metadata for recoverab const source = await import(`node:fs/promises`).then((fs) => fs.readFile(new URL("../auto.ts", import.meta.url), "utf-8") ); + assert.ok(source.includes('const shouldResumePausedSession =')); assert.ok(source.includes('freshStartAssessment.classification === "recoverable"')); + assert.ok(source.includes('&& (')); assert.ok(source.includes('freshStartAssessment.hasResumableDiskState')); assert.ok(source.includes('|| !!freshStartAssessment.recoveryPrompt')); assert.ok(source.includes('|| !!freshStartAssessment.lock')); - assert.ok(!source.includes('freshStartAssessment.classification === "recoverable"\n || freshStartAssessment.hasResumableDiskState')); }); test("auto module imports successfully after interrupted-session changes", async () => {