diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index b70fd3fd9..04073b611 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -909,8 +909,9 @@ export async function showSmartEntry( } } - const state = interrupted.state ?? await deriveState(basePath); - + // Always derive from the project root — the assessment may have derived + // state from a worktree path that was cleaned up in the stale branch above. + const state = await deriveState(basePath); if (!state.activeMilestone) { // Guard: if a discuss session is already in flight, don't re-inject the prompt. diff --git a/src/resources/extensions/gsd/tests/crash-recovery.test.ts b/src/resources/extensions/gsd/tests/crash-recovery.test.ts index 132dbec9b..c396ee728 100644 --- a/src/resources/extensions/gsd/tests/crash-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/crash-recovery.test.ts @@ -168,6 +168,25 @@ test("readPausedSessionMetadata reads paused-session metadata when present", () } }); +test("assessInterruptedSession returns none when no lock and no paused session exist", async () => { + const base = makeTmpBase(); + try { + const assessment = await assessInterruptedSession(base); + assert.equal(assessment.classification, "none"); + assert.equal(assessment.lock, null); + assert.equal(assessment.pausedSession, null); + assert.equal(assessment.state, null); + assert.equal(assessment.recovery, null); + assert.equal(assessment.recoveryPrompt, null); + assert.equal(assessment.recoveryToolCallCount, 0); + assert.equal(assessment.artifactSatisfied, false); + assert.equal(assessment.hasResumableDiskState, false); + assert.equal(assessment.isBootstrapCrash, false); + } finally { + cleanup(base); + } +}); + test("assessInterruptedSession classifies stale complete repo as stale and suppresses recovery", async () => { const base = makeTmpBase(); try {