@@ -21,6 +21,12 @@ export class LocalBackgroundHandle implements BackgroundHandle {
2121
2222 constructor (
2323 private readonly pid : number ,
24+ /**
25+ * Process group ID for termination.
26+ * - Unix (setsid=true): equals pid, since setsid makes process a session/group leader
27+ * - Windows MSYS2 (setsid=false): actual PGID from /proc, used with kill -PGID
28+ */
29+ private readonly pgid : number ,
2430 public readonly outputDir : string
2531 ) { }
2632
@@ -53,31 +59,23 @@ export class LocalBackgroundHandle implements BackgroundHandle {
5359
5460 const exitCodePath = path . join ( this . outputDir , "exit_code" ) ;
5561
56- // Windows: convert MSYS2 PID to Windows PID and use taskkill
57- // Must run via bash because /proc/PID/winpid is MSYS2's virtual filesystem,
58- // not visible to Node.js running as a native Windows process
62+ // Windows: use MSYS2's kill command with negative PGID to kill process group
63+ // taskkill doesn't work because MSYS2's process tree doesn't match Windows' process tree
64+ // MSYS2's kill understands its own process groups, so kill -PGID works correctly
5965 if ( process . platform === "win32" ) {
6066 try {
61- // Script reads winpid from MSYS2's /proc, then uses taskkill
62- // taskkill /F = force, /T = kill child processes
63- // Double slashes needed because bash interprets single slash as path
64- const terminateScript = `
65- if [ -f /proc/${ this . pid } /winpid ]; then
66- winpid=$(cat /proc/${ this . pid } /winpid)
67- taskkill //PID $winpid //F //T 2>/dev/null
68- fi
69- kill -9 ${ this . pid } 2>/dev/null || true
70- ` ;
71- log . debug ( `LocalBackgroundHandle: Terminating MSYS2 PID ${ this . pid } via bash` ) ;
67+ // Use PGID to kill entire process group via MSYS2's kill command
68+ const terminateScript = `kill -9 -${ this . pgid } 2>/dev/null || true` ;
69+ log . debug ( `LocalBackgroundHandle: Terminating MSYS2 process group ${ this . pgid } via bash` ) ;
7270 using proc = execAsync ( terminateScript , { shell : getBashPath ( ) } ) ;
7371 await proc . result ;
7472 } catch ( error ) {
75- // Process already dead or winpid unavailable
73+ // Process already dead
7674 log . debug (
7775 `LocalBackgroundHandle: Windows terminate error: ${ error instanceof Error ? error . message : String ( error ) } `
7876 ) ;
7977 }
80- // Write exit code - trap may not fire with taskkill
78+ // Write exit code - trap may not fire with kill -9
8179 try {
8280 await fs . access ( exitCodePath ) ;
8381 } catch {
@@ -89,11 +87,11 @@ export class LocalBackgroundHandle implements BackgroundHandle {
8987 }
9088
9189 // Unix: use process group signals
92- const pgid = - this . pid ; // Negative PID = process group
90+ const negativePgid = - this . pgid ; // Negative PGID = process group
9391
9492 try {
95- log . debug ( `LocalBackgroundHandle: Sending SIGTERM to process group ${ pgid } ` ) ;
96- process . kill ( pgid , "SIGTERM" ) ;
93+ log . debug ( `LocalBackgroundHandle: Sending SIGTERM to process group ${ negativePgid } ` ) ;
94+ process . kill ( negativePgid , "SIGTERM" ) ;
9795
9896 // Wait 2 seconds for graceful shutdown
9997 await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ;
@@ -108,8 +106,8 @@ export class LocalBackgroundHandle implements BackgroundHandle {
108106 }
109107
110108 if ( stillRunning ) {
111- log . debug ( `LocalBackgroundHandle: Process still running, sending SIGKILL to group ${ pgid } ` ) ;
112- process . kill ( pgid , "SIGKILL" ) ;
109+ log . debug ( `LocalBackgroundHandle: Process still running, sending SIGKILL to group ${ negativePgid } ` ) ;
110+ process . kill ( negativePgid , "SIGKILL" ) ;
113111
114112 // Write exit code for SIGKILL since we had to force kill
115113 await fs . writeFile ( exitCodePath , String ( EXIT_CODE_SIGKILL ) ) . catch ( ( ) => {
0 commit comments