Files
oh-my-opencode/src/cli/doctor/spawn-with-timeout.ts
T
YeonGyu-Kim d21ed3f765 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 and proper
  cleanup (timer cleared on success, proc.exited awaited after kill)
- Capture both stdout and stderr to preserve gh auth status behavior
- Add 30s overall doctor command timeout with JSON-mode support
- Distinguish timeout errors from other failures in runner
- Update all doctor check subprocess calls to use timeouts
2026-04-11 23:15:09 +09:00

48 lines
1.4 KiB
TypeScript

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 interface SpawnWithTimeoutResult {
stdout: string
stderr: string
exitCode: number
timedOut: boolean
}
export async function spawnWithTimeout(
command: string[],
options: SpawnOptions,
timeoutMs: number = DEFAULT_SPAWN_TIMEOUT_MS
): Promise<SpawnWithTimeoutResult> {
let proc: ReturnType<typeof spawnWithWindowsHide>
try {
proc = spawnWithWindowsHide(command, options)
} catch {
return { stdout: "", stderr: "", exitCode: 1, timedOut: false }
}
let timer: ReturnType<typeof setTimeout> | undefined
const timeoutPromise = new Promise<"timeout">((resolve) => {
timer = 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")
await proc.exited.catch(() => {})
return { stdout: "", stderr: "", exitCode: 1, timedOut: true }
}
clearTimeout(timer)
const stdout = proc.stdout ? await new Response(proc.stdout).text() : ""
const stderr = proc.stderr ? await new Response(proc.stderr).text() : ""
return { stdout, stderr, exitCode: proc.exitCode ?? 1, timedOut: false }
}