From 432cb79097e1af3ddfb0340c265ea4a2c1bdc995 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 18:55:33 -0700 Subject: [PATCH 1/4] fix(gsd): auto-checkout to main when isolation:none finds stale milestone branch When switching from isolation:branch/worktree to isolation:none, HEAD could remain on a milestone/ branch from the prior session. All subsequent auto-mode commits would silently land on the wrong branch. Now auto-start checks for stale milestone branches when isolation:none and auto-checks out to the integration branch (main/master). Closes #3613 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-start.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/resources/extensions/gsd/auto-start.ts b/src/resources/extensions/gsd/auto-start.ts index 8751d51c7..e73583954 100644 --- a/src/resources/extensions/gsd/auto-start.ts +++ b/src/resources/extensions/gsd/auto-start.ts @@ -44,6 +44,9 @@ import { nativeInit, nativeAddAll, nativeCommit, + nativeGetCurrentBranch, + nativeDetectMainBranch, + nativeCheckoutBranch, } from "./native-git-bridge.js"; import { GitServiceImpl } from "./git-service.js"; import { @@ -528,6 +531,22 @@ export async function bootstrapAutoSession( setActiveMilestoneId(base, s.currentMilestoneId); } + // Guard against stale milestone branch when isolation:none (#3613). + // A prior session with isolation:branch/worktree may have left HEAD on + // milestone/. Auto-checkout back to the integration branch. + if (getIsolationMode() === "none" && nativeIsRepo(base)) { + try { + const currentBranch = nativeGetCurrentBranch(base); + if (currentBranch.startsWith("milestone/")) { + const integrationBranch = nativeDetectMainBranch(base); + nativeCheckoutBranch(base, integrationBranch); + logWarning("autoStart", `Returned to "${integrationBranch}" — HEAD was on stale milestone branch "${currentBranch}" (isolation: none does not use milestone branches).`); + } + } catch { + // Non-fatal — log and continue; user may need to manually checkout + } + } + // ── Auto-worktree setup ── s.originalBasePath = base; From 5a0c1275433237439d88d616f1e91e54c2941155 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:25:57 -0700 Subject: [PATCH 2/4] test: add regression test for isolation:none stale branch guard Co-Authored-By: Claude Opus 4.6 (1M context) --- .../tests/isolation-none-branch-guard.test.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts diff --git a/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts b/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts new file mode 100644 index 000000000..5acf71583 --- /dev/null +++ b/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts @@ -0,0 +1,62 @@ +/** + * Regression test for #3675 — isolation:none stale branch guard + * + * When switching from isolation:branch/worktree to isolation:none, HEAD + * could remain on a milestone/ branch. The fix in auto-start.ts + * detects this and auto-checks out to the integration branch. + * + * This structural test verifies the milestone/ branch check exists + * in auto-start.ts. + */ + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const source = readFileSync(join(__dirname, '..', 'auto-start.ts'), 'utf-8'); + +describe('isolation:none stale branch guard (#3675)', () => { + test('checks for milestone/ branch prefix', () => { + assert.match(source, /startsWith\(["']milestone\//, + 'auto-start should check for milestone/ branch prefix'); + }); + + test('imports nativeGetCurrentBranch', () => { + assert.match(source, /nativeGetCurrentBranch/, + 'auto-start should import nativeGetCurrentBranch'); + }); + + test('imports nativeDetectMainBranch', () => { + assert.match(source, /nativeDetectMainBranch/, + 'auto-start should import nativeDetectMainBranch'); + }); + + test('imports nativeCheckoutBranch', () => { + assert.match(source, /nativeCheckoutBranch/, + 'auto-start should import nativeCheckoutBranch'); + }); + + test('guard is conditional on isolation mode "none"', () => { + assert.match(source, /getIsolationMode\(\)\s*===\s*["']none["']/, + 'guard should only activate when isolation mode is "none"'); + }); + + test('calls nativeCheckoutBranch to return to integration branch', () => { + assert.match(source, /nativeCheckoutBranch\(base,\s*integrationBranch\)/, + 'should checkout to the integration branch'); + }); + + test('guard is wrapped in try-catch (non-fatal)', () => { + // Find the milestone/ check and verify it is inside a try block + const milestoneIdx = source.indexOf('startsWith("milestone/")'); + assert.ok(milestoneIdx > 0, 'milestone/ check should exist'); + const before = source.slice(Math.max(0, milestoneIdx - 500), milestoneIdx); + assert.match(before, /try\s*\{/, + 'milestone branch guard should be inside a try block'); + }); +}); From 6bb5a3fe9a046f30e1cbccadd16570f707e19796 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:45:03 -0700 Subject: [PATCH 3/4] fix(gsd): use valid LogComponent type for stale branch guard warning Change logWarning component from "autoStart" (not in LogComponent union) to "bootstrap" to fix TypeScript compilation error. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-start.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/auto-start.ts b/src/resources/extensions/gsd/auto-start.ts index e73583954..4821a90d3 100644 --- a/src/resources/extensions/gsd/auto-start.ts +++ b/src/resources/extensions/gsd/auto-start.ts @@ -540,7 +540,7 @@ export async function bootstrapAutoSession( if (currentBranch.startsWith("milestone/")) { const integrationBranch = nativeDetectMainBranch(base); nativeCheckoutBranch(base, integrationBranch); - logWarning("autoStart", `Returned to "${integrationBranch}" — HEAD was on stale milestone branch "${currentBranch}" (isolation: none does not use milestone branches).`); + logWarning("bootstrap", `Returned to "${integrationBranch}" — HEAD was on stale milestone branch "${currentBranch}" (isolation: none does not use milestone branches).`); } } catch { // Non-fatal — log and continue; user may need to manually checkout From 8f834e23fe67d3489a46df3f61947b53191dad51 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 23:00:06 -0700 Subject: [PATCH 4/4] fix: replace empty catch with logWarning for silent-catch-diagnostics test --- src/resources/extensions/gsd/auto-start.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/auto-start.ts b/src/resources/extensions/gsd/auto-start.ts index 4821a90d3..a3d0232d5 100644 --- a/src/resources/extensions/gsd/auto-start.ts +++ b/src/resources/extensions/gsd/auto-start.ts @@ -542,8 +542,8 @@ export async function bootstrapAutoSession( nativeCheckoutBranch(base, integrationBranch); logWarning("bootstrap", `Returned to "${integrationBranch}" — HEAD was on stale milestone branch "${currentBranch}" (isolation: none does not use milestone branches).`); } - } catch { - // Non-fatal — log and continue; user may need to manually checkout + } catch (err) { + logWarning("bootstrap", `Could not auto-checkout from stale milestone branch: ${err instanceof Error ? err.message : String(err)}`); } }