Merge pull request #3675 from Tibsfox/fix/isolation-none-stale-branch-guard

fix(gsd): auto-checkout to main when isolation:none finds stale milestone branch
This commit is contained in:
Jeremy McSpadden 2026-04-07 07:07:22 -05:00 committed by GitHub
commit 7420399125
2 changed files with 81 additions and 0 deletions

View file

@ -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/<MID>. 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("bootstrap", `Returned to "${integrationBranch}" — HEAD was on stale milestone branch "${currentBranch}" (isolation: none does not use milestone branches).`);
}
} catch (err) {
logWarning("bootstrap", `Could not auto-checkout from stale milestone branch: ${err instanceof Error ? err.message : String(err)}`);
}
}
// ── Auto-worktree setup ──
s.originalBasePath = base;

View file

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