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.
This commit is contained in:
Derek Pearson 2026-03-21 14:03:18 -04:00
parent 2b4e78d9ab
commit 9992d24649
2 changed files with 30 additions and 13 deletions

View file

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

View file

@ -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 () => {