fix(#3050): consolidate the spawn-timeout predicate and propagate the unresolved-root reason (#3060)

* chore(#3050): changeset and review artifacts for the follow-up

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#3050): backfill changeset pr number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-08-04 17:21:24 -04:00
committed by GitHub
parent 24066e536e
commit 4eb8e3648c
15 changed files with 530 additions and 253 deletions

View File

@@ -3838,3 +3838,56 @@ describe('gsd-tools.cjs dispatch/help/skip-list parity (DEFECT.GENERATIVE-FIX, #
assert.equal(typeof gsdTools.skipsRootResolution, 'function');
});
});
// ─────────────────────────────────────────────────────────────────────────────
// resolveMainWorktreeCwd — #3050: gsd-tools must surface (not silently
// swallow) a git_timed_out worktree-root resolution, since writing planning
// artifacts to the wrong tree is the exact fail-open the issue names.
// ─────────────────────────────────────────────────────────────────────────────
describe('gsd-tools.cjs resolveMainWorktreeCwd (#3050)', () => {
const { resolveMainWorktreeCwd } = require('../gsd-core/bin/gsd-tools.cjs');
test('emits a WARNING to stderr and still resolves the fallback root on git_timed_out', () => {
const warnings = [];
const resolved = resolveMainWorktreeCwd('/repo/wt', {
existsSync: () => false,
resolveWorktreeRoot: () => ({ root: '/repo/wt', reason: 'git_timed_out' }),
writeWarning: (msg) => warnings.push(msg),
});
assert.equal(resolved, '/repo/wt');
assert.equal(warnings.length, 1, 'must emit exactly one warning on git_timed_out');
assert.match(warnings[0], /git timed out/i);
assert.match(warnings[0], /wrong tree/i);
});
test('does NOT warn on a benign reason (linked_worktree)', () => {
const warnings = [];
const resolved = resolveMainWorktreeCwd('/repo/wt', {
existsSync: () => false,
resolveWorktreeRoot: () => ({ root: '/repo', reason: 'linked_worktree' }),
writeWarning: (msg) => warnings.push(msg),
});
assert.equal(resolved, '/repo');
assert.deepEqual(warnings, [], 'must not warn for a benign, definitive resolution');
});
test('does NOT warn on a benign reason (not_git_repo)', () => {
const warnings = [];
const resolved = resolveMainWorktreeCwd('/repo/wt', {
existsSync: () => false,
resolveWorktreeRoot: () => ({ root: '/repo/wt', reason: 'not_git_repo' }),
writeWarning: (msg) => warnings.push(msg),
});
assert.equal(resolved, '/repo/wt');
assert.deepEqual(warnings, []);
});
test('short-circuits (never calls resolveWorktreeRoot) when .planning already exists in cwd', () => {
const resolved = resolveMainWorktreeCwd('/repo/wt', {
existsSync: () => true,
resolveWorktreeRoot: () => { throw new Error('must not be called when .planning exists locally'); },
writeWarning: () => { throw new Error('must not warn when short-circuited'); },
});
assert.equal(resolved, '/repo/wt');
});
});