mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
Round-3 review found the guard was not the choke point its own comments claimed: six pid-addressed `taskkill /pid <pid> /t /f` families in main were ungated and uninstrumented, so the stale-pid shape stayed producible and a `selfInitiatedTreeKillCount: 0` could read as exculpatory when it was not. - Gate the remaining main-process families: the git command-runner abort, the notebook-cell and automation-precheck timeouts. - Turn the `src/shared` seam into the gate itself (`process-tree-kill-gate`), so the runProcess choke point, the codex app-server deadline kill and the ephemeral-VM recipe kill ask the same decision. Those three are compiled into the CLI/relay too and cannot import main; main installs the guard at preflight. - Ratchet (`main-process-tree-kill-gate.test.ts`): a new pid-addressed taskkill in main that skips the gate fails, and the allowlist entries must still exist. - Give pid-addressed kills eviction priority in the 32-entry ring: 32 routine `win-pty-job` teardowns from a window-close burst no longer evict the one entry that discriminates a self-kill from an external one. - Correct the coverage doc, which described the uninstrumented Windows sites as POSIX `process.kill(-pid)` group kills and omitted the git and codex paths.
60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
import { spawn, type ChildProcess } from 'node:child_process'
|
|
import { admitSelfInitiatedTreeKill } from '../../own-chromium-tree-kill-guard'
|
|
|
|
const WINDOWS_TREE_KILL_WAIT_MS = 2_000
|
|
|
|
export function killSpawnedCommandTree(child: ChildProcess): Promise<void> {
|
|
const pid = child.pid
|
|
if (!pid || process.platform !== 'win32') {
|
|
child.kill()
|
|
return Promise.resolve()
|
|
}
|
|
if (
|
|
!admitSelfInitiatedTreeKill({ pid, site: 'git-command-tree-kill', scope: 'win-taskkill-tree' })
|
|
) {
|
|
return Promise.resolve()
|
|
}
|
|
return new Promise((resolve) => {
|
|
let killer: ChildProcess
|
|
try {
|
|
// Why: Windows shims/wsl.exe own descendants; wait for /t tree cleanup so a timed-out command can't outlive its probe.
|
|
killer = spawn('taskkill', ['/pid', String(pid), '/t', '/f'], {
|
|
stdio: 'ignore',
|
|
windowsHide: true
|
|
})
|
|
if (!killer || typeof killer.unref !== 'function') {
|
|
child.kill()
|
|
resolve()
|
|
return
|
|
}
|
|
} catch {
|
|
child.kill()
|
|
resolve()
|
|
return
|
|
}
|
|
let settled = false
|
|
let timer: NodeJS.Timeout | null = null
|
|
const finish = (fallbackToChildKill: boolean): void => {
|
|
if (settled) {
|
|
return
|
|
}
|
|
settled = true
|
|
if (timer) {
|
|
clearTimeout(timer)
|
|
}
|
|
killer.removeAllListeners()
|
|
if (fallbackToChildKill) {
|
|
child.kill()
|
|
}
|
|
resolve()
|
|
}
|
|
killer.once('error', () => finish(true))
|
|
killer.once('close', (code) => finish(code !== 0))
|
|
timer = setTimeout(() => {
|
|
killer.kill()
|
|
finish(true)
|
|
}, WINDOWS_TREE_KILL_WAIT_MS)
|
|
killer.unref()
|
|
})
|
|
}
|