fix(doctor): add timeouts to subprocess spawns to prevent exit code 137

Doctor checks spawned subprocesses (gh, opencode, sg) without timeouts,
causing the process to hang indefinitely if any binary was stuck.
The OS would then SIGKILL the process (exit code 137).

- Add spawnWithTimeout utility with 10s per-spawn timeout
- Add 30s overall doctor command timeout with graceful error message
- Update all doctor check subprocess calls to use timeouts
This commit is contained in:
YeonGyu-Kim
2026-04-11 23:04:03 +09:00
parent 44ebf4b809
commit 69f1c9ae35
6 changed files with 141 additions and 34 deletions
+36
View File
@@ -0,0 +1,36 @@
import type { SpawnOptions } from "../../shared/spawn-with-windows-hide"
import { spawnWithWindowsHide } from "../../shared/spawn-with-windows-hide"
const DEFAULT_SPAWN_TIMEOUT_MS = 10_000
export async function spawnWithTimeout(
command: string[],
options: SpawnOptions,
timeoutMs: number = DEFAULT_SPAWN_TIMEOUT_MS
): Promise<{ stdout: string; exitCode: number; timedOut: boolean }> {
let proc: ReturnType<typeof spawnWithWindowsHide>
try {
proc = spawnWithWindowsHide(command, options)
} catch {
return { stdout: "", exitCode: 1, timedOut: false }
}
const timeoutPromise = new Promise<"timeout">((resolve) => {
setTimeout(() => resolve("timeout"), timeoutMs)
})
const processPromise = (async (): Promise<"done"> => {
await proc.exited
return "done"
})()
const race = await Promise.race([processPromise, timeoutPromise])
if (race === "timeout") {
proc.kill("SIGTERM")
return { stdout: "", exitCode: 1, timedOut: true }
}
const stdout = proc.stdout ? await new Response(proc.stdout).text() : ""
return { stdout, exitCode: proc.exitCode ?? 1, timedOut: false }
}