extraction/index.ts bounds every git call it makes; worktree.ts, git-hooks.ts, and the installer's npm install -g did not, so a stuck subprocess blocked the caller indefinitely. Worst case was the daemon: gitWorktreeRoot/gitCommonDir run (memoized) on the main event loop while serving MCP clients, where an unbounded git hang would trip the 60s liveness watchdog and SIGKILL a healthy daemon. git calls get 5s, the interactive npm install 120s. Regression tests assert the option through a mocked child_process plus a per-file call-site sweep. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
713ab7af43
commit
2f70eb3d32
@@ -115,7 +115,9 @@ export async function runInstallerWithOptions(opts: RunInstallerOptions): Promis
|
||||
const s = clack.spinner();
|
||||
s.start('Installing codegraph CLI...');
|
||||
try {
|
||||
execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe', windowsHide: true });
|
||||
// Generous bound (slow networks / cold npm cache) — but bounded, so a
|
||||
// wedged npm can't hang the interactive installer forever (#1139).
|
||||
execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe', windowsHide: true, timeout: 120_000 });
|
||||
s.stop('Installed codegraph CLI on PATH');
|
||||
} catch {
|
||||
s.stop('Could not install (permission denied)');
|
||||
|
||||
@@ -45,6 +45,7 @@ export function isGitRepo(projectRoot: string): boolean {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
windowsHide: true,
|
||||
timeout: 5000, // fail fast instead of hanging init/sync on a stuck git (#1139)
|
||||
}).trim();
|
||||
return out === 'true';
|
||||
} catch {
|
||||
@@ -63,6 +64,7 @@ function gitHooksDir(projectRoot: string): string | null {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
windowsHide: true,
|
||||
timeout: 5000, // same rationale as isGitRepo
|
||||
}).trim();
|
||||
if (!out) return null;
|
||||
return path.isAbsolute(out) ? out : path.resolve(projectRoot, out);
|
||||
|
||||
@@ -36,6 +36,10 @@ export function gitWorktreeRoot(dir: string): string | null {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
windowsHide: true,
|
||||
// Bounded like every git call in extraction/: this runs (memoized) on
|
||||
// the daemon's main event loop, where an unbounded hang would trip the
|
||||
// 60s liveness watchdog and SIGKILL a healthy daemon (#1139).
|
||||
timeout: 5000,
|
||||
}).trim();
|
||||
return out ? realpath(out) : null;
|
||||
} catch {
|
||||
@@ -58,6 +62,7 @@ export function gitCommonDir(dir: string): string | null {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
windowsHide: true,
|
||||
timeout: 5000, // same rationale as gitWorktreeRoot
|
||||
}).trim();
|
||||
if (!out) return null;
|
||||
// `--git-common-dir` is relative to cwd unless already absolute.
|
||||
|
||||
Reference in New Issue
Block a user