diff --git a/src/cli.ts b/src/cli.ts index 6b96cbc5..d07dccc7 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -175,7 +175,15 @@ program .command('hooks-run', { hidden: true }) .description('Internal: the pre-commit context guard the git hook invokes') .option('--strict', 'Exit non-zero on a score regression') - .action((options) => hooksRun(options)); + .action((options) => { + hooksRun(options); + // The guard runs inside `git commit`: once its line is written, exit, so it + // can never hold a commit open waiting on a handle (#167). The blocked path + // already exits inside hooksRun; every path that returns here allows the commit. + const done = (): never => process.exit(0); + setTimeout(done, 2000).unref(); + process.stdout.write('', () => process.stderr.write('', done)); + }); program .command('export') diff --git a/tests/wjttc/hooks.test.ts b/tests/wjttc/hooks.test.ts index 7bbf0e5a..72acb99a 100644 --- a/tests/wjttc/hooks.test.ts +++ b/tests/wjttc/hooks.test.ts @@ -30,7 +30,7 @@ const CLI = join(import.meta.dir, '../../src/cli.ts'); const shPath = (p: string): string => p.replace(/\\/g, '/'); const LOCAL_RUNNER = `${shPath(process.execPath)} ${shPath(CLI)} hooks-run`; // points the hook at THIS cli -const mk = (tag: string) => { +const mk = (tag = 'case') => { let dir = join(tmpdir(), `faf-hooks-${tag}-${Date.now()}-${Math.random().toString(36).slice(2)}`); mkdirSync(dir, { recursive: true }); dir = realpathSync.native(dir); // resolve symlinks AND Windows 8.3 short names (RUNNER~1) → matches git @@ -47,7 +47,7 @@ const commit = (dir: string, msg: string, flags: string[] = []): { ok: boolean; // the warn-mode ⚠ lands on the hook's stderr even when the commit is allowed. // A timeout turns a hook that never exits into a named failure, not a stalled suite. const r = spawnSync('git', ['commit', '-m', msg, ...flags], { cwd: dir, encoding: 'utf-8', timeout: spawnLimit(60_000) }); - if (r.error) {throw new Error(`git commit did not finish (${r.error.message}); output so far: ${r.stdout ?? ''}${r.stderr ?? ''}`);} + if (r.error) {throw new Error(`hung child process (#167): git commit did not finish (${r.error.message}); output so far: ${r.stdout ?? ''}${r.stderr ?? ''}`);} return { ok: r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}` }; }; const capture = (fn: () => void): string => {