11import { describe , it , expect } from "vitest" ;
2+ import fs from "fs" ;
23import { exec as rawExec } from "../src/utils/agent/sandbox/raw-exec.js" ;
34import type { AppConfig } from "src/utils/config.js" ;
45
@@ -37,7 +38,11 @@ describe("rawExec – abort kills entire process group", () => {
3738 // - spawns a background `sleep 30`
3839 // - prints the PID of the `sleep`
3940 // - waits for `sleep` to exit
40- const { stdout, exitCode } = await ( async ( ) => {
41+ const {
42+ stdout,
43+ exitCode,
44+ pid : rootPid ,
45+ } = await ( async ( ) => {
4146 const p = rawExec ( cmd , { } , config , abortController . signal ) ;
4247
4348 // Give Bash a tiny bit of time to start and print the PID.
@@ -52,6 +57,7 @@ describe("rawExec – abort kills entire process group", () => {
5257
5358 // We expect a non‑zero exit code because the process was killed.
5459 expect ( exitCode ) . not . toBe ( 0 ) ;
60+ expect ( rootPid ) . toBeGreaterThan ( 0 ) ;
5561
5662 // Extract the PID of the sleep process that bash printed
5763 const pid = Number ( stdout . trim ( ) . match ( / ^ \d + / ) ?. [ 0 ] ) ;
@@ -68,11 +74,19 @@ describe("rawExec – abort kills entire process group", () => {
6874 * @throws {Error } If the process is still alive after 500ms
6975 */
7076async function ensureProcessGone ( pid : number ) {
71- const timeout = 500 ;
77+ const timeout = 1000 ;
7278 const deadline = Date . now ( ) + timeout ;
7379 while ( Date . now ( ) < deadline ) {
7480 try {
7581 process . kill ( pid , 0 ) ; // check if process still exists
82+ try {
83+ const stat = await fs . promises . readFile ( `/proc/${ pid } /stat` , "utf8" ) ;
84+ if ( stat . split ( " " ) [ 2 ] === "Z" ) {
85+ return ; // zombie processes are effectively dead
86+ }
87+ } catch {
88+ /* ignore */
89+ }
7690 await new Promise ( ( r ) => setTimeout ( r , 50 ) ) ; // wait and retry
7791 } catch ( e : any ) {
7892 if ( e . code === "ESRCH" ) {
@@ -81,6 +95,23 @@ async function ensureProcessGone(pid: number) {
8195 throw e ; // unexpected error — rethrow
8296 }
8397 }
98+ try {
99+ process . kill ( pid , "SIGKILL" ) ;
100+ } catch {
101+ /* ignore */
102+ }
103+ const extraDeadline = Date . now ( ) + 250 ;
104+ while ( Date . now ( ) < extraDeadline ) {
105+ try {
106+ process . kill ( pid , 0 ) ;
107+ await new Promise ( ( r ) => setTimeout ( r , 50 ) ) ;
108+ } catch ( e : any ) {
109+ if ( e . code === "ESRCH" ) {
110+ return ;
111+ }
112+ throw e ;
113+ }
114+ }
84115 throw new Error (
85116 `Process with PID ${ pid } failed to terminate within ${ timeout } ms` ,
86117 ) ;
0 commit comments