fix(worktree): address PR review — test assertion, dry-run sentinel, git timeouts

- Update allProjects test expectation to match [parent, composite] (matches JSDoc + callers in ContextBuilder/context handlers).
- Replace string-matched __DRY_RUN_ROLLBACK__ sentinel with dedicated DryRunRollback class to avoid swallowing unrelated errors.
- Add 5000ms timeout to spawnSync git calls in WorktreeAdoption and ProcessManager so worker startup can't hang on a stuck git process.
- Drop unreachable break after process.exit(0) in adopt case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-16 19:50:01 -07:00
parent 0a5f072aaf
commit d24f3a7019
5 changed files with 139 additions and 126 deletions

File diff suppressed because one or more lines are too long

View File

@@ -685,7 +685,10 @@ type CwdClassification =
| { kind: 'skip' };
function gitQuery(cwd: string, args: string[]): string | null {
const r = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8' });
const r = spawnSync('git', ['-C', cwd, ...args], {
encoding: 'utf8',
timeout: 5000
});
if (r.status !== 0) return null;
return (r.stdout ?? '').trim();
}

View File

@@ -48,8 +48,20 @@ interface WorktreeEntry {
branch: string | null;
}
const GIT_TIMEOUT_MS = 5000;
class DryRunRollback extends Error {
constructor() {
super('dry-run rollback');
this.name = 'DryRunRollback';
}
}
function gitCapture(cwd: string, args: string[]): string | null {
const r = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8' });
const r = spawnSync('git', ['-C', cwd, ...args], {
encoding: 'utf8',
timeout: GIT_TIMEOUT_MS
});
if (r.status !== 0) return null;
return (r.stdout ?? '').trim();
}
@@ -221,16 +233,15 @@ export async function adoptMergedWorktrees(opts: {
}
}
if (dryRun) {
// Throw to force rollback. Sentinel caught below.
throw new Error('__DRY_RUN_ROLLBACK__');
// Throw a dedicated error to force rollback. Caught below by instanceof check.
throw new DryRunRollback();
}
});
try {
tx();
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message === '__DRY_RUN_ROLLBACK__') {
if (err instanceof DryRunRollback) {
// Rolled back as intended for dry-run — counts are still useful.
} else {
throw err;

View File

@@ -1235,7 +1235,6 @@ async function main() {
console.log(` ! ${err.worktree}: ${err.error}`);
}
process.exit(0);
break;
}
case '--daemon':

View File

@@ -130,7 +130,7 @@ describe('getProjectContext', () => {
expect(ctx.isWorktree).toBe(true);
expect(ctx.primary).toBe('main-repo/my-worktree');
expect(ctx.parent).toBe('main-repo');
expect(ctx.allProjects).toEqual(['main-repo/my-worktree']);
expect(ctx.allProjects).toEqual(['main-repo', 'main-repo/my-worktree']);
});
it('write-path call sites resolve to composite name in worktrees', () => {