@@ -218,6 +218,13 @@ export const BashTool = Tool.define("bash", async () => {
218218 }
219219 }
220220
221+ log . info ( "spawned process" , {
222+ pid : proc . pid ,
223+ command : params . command . slice ( 0 , 100 ) ,
224+ cwd,
225+ timeout,
226+ } )
227+
221228 const MAX_OUTPUT_BYTES = 10 * 1024 * 1024 // 10 MB cap
222229 const outputChunks : Buffer [ ] = [ ]
223230 let outputLen = 0
@@ -263,23 +270,27 @@ export const BashTool = Tool.define("bash", async () => {
263270 }
264271
265272 const abortHandler = ( ) => {
273+ log . info ( "process abort triggered" , { pid : proc . pid } )
266274 aborted = true
267275 void kill ( )
268276 }
269277
270278 ctx . abort . addEventListener ( "abort" , abortHandler , { once : true } )
271279
272280 const timeoutTimer = setTimeout ( ( ) => {
281+ log . info ( "process timeout triggered" , { pid : proc . pid , timeout } )
273282 timedOut = true
274283 void kill ( )
275284 } , timeout + 100 )
276285
286+ const started = Date . now ( )
287+
277288 const callID = ctx . callID
278289 if ( callID ) {
279290 active . set ( callID , {
280291 pid : proc . pid ! ,
281292 timeout,
282- started : Date . now ( ) ,
293+ started,
283294 kill : ( ) => Shell . killTree ( proc , { exited : ( ) => exited } ) ,
284295 done : ( ) => { } ,
285296 } )
@@ -294,6 +305,8 @@ export const BashTool = Tool.define("bash", async () => {
294305 clearTimeout ( timeoutTimer )
295306 clearInterval ( poll )
296307 ctx . abort . removeEventListener ( "abort" , abortHandler )
308+ proc . stdout ?. removeListener ( "end" , check )
309+ proc . stderr ?. removeListener ( "end" , check )
297310 }
298311
299312 const done = ( ) => {
@@ -316,21 +329,62 @@ export const BashTool = Tool.define("bash", async () => {
316329 reject ( error )
317330 }
318331
319- proc . once ( "exit" , done )
320- proc . once ( "close" , done )
332+ proc . once ( "exit" , ( ) => {
333+ log . info ( "process exit detected via 'exit' event" , { pid : proc . pid , exitCode : proc . exitCode } )
334+ done ( )
335+ } )
336+ proc . once ( "close" , ( ) => {
337+ log . info ( "process exit detected via 'close' event" , { pid : proc . pid , exitCode : proc . exitCode } )
338+ done ( )
339+ } )
321340 proc . once ( "error" , fail )
322341
342+ // Redundancy: stdio end events fire when pipe file descriptors close
343+ // independent of process exit monitoring — catches missed exit events
344+ let streams = 0
345+ const total = ( proc . stdout ? 1 : 0 ) + ( proc . stderr ? 1 : 0 )
346+ const check = ( ) => {
347+ streams ++
348+ if ( streams < total ) return
349+ if ( proc . exitCode !== null || proc . signalCode !== null ) {
350+ log . info ( "stdio end detected exit (exitCode already set)" , {
351+ pid : proc . pid ,
352+ exitCode : proc . exitCode ,
353+ } )
354+ done ( )
355+ return
356+ }
357+ setTimeout ( ( ) => {
358+ log . info ( "stdio end deferred check" , {
359+ pid : proc . pid ,
360+ exitCode : proc . exitCode ,
361+ } )
362+ done ( )
363+ } , 50 )
364+ }
365+ proc . stdout ?. once ( "end" , check )
366+ proc . stderr ?. once ( "end" , check )
367+
323368 // Polling watchdog: detect process exit when Bun's event loop
324369 // fails to deliver the "exit" event (confirmed Bun bug in containers)
325370 const poll = setInterval ( ( ) => {
326371 if ( proc . exitCode !== null || proc . signalCode !== null ) {
372+ log . info ( "polling watchdog detected exit via exitCode/signalCode" , {
373+ exitCode : proc . exitCode ,
374+ signalCode : proc . signalCode ,
375+ } )
327376 done ( )
328377 return
329378 }
379+
380+ // Check 2: process.kill(pid, 0) throws ESRCH if process is dead
330381 if ( proc . pid && process . platform !== "win32" ) {
331382 try {
332383 process . kill ( proc . pid , 0 )
333384 } catch {
385+ log . info ( "polling watchdog detected exit via kill(0) ESRCH" , {
386+ pid : proc . pid ,
387+ } )
334388 done ( )
335389 return
336390 }
@@ -340,6 +394,14 @@ export const BashTool = Tool.define("bash", async () => {
340394
341395 if ( callID ) active . delete ( callID )
342396
397+ log . info ( "process completed" , {
398+ pid : proc . pid ,
399+ exitCode : proc . exitCode ,
400+ duration : Date . now ( ) - started ,
401+ timedOut,
402+ aborted,
403+ } )
404+
343405 let output = Buffer . concat ( outputChunks ) . toString ( )
344406 // Free the chunks array
345407 outputChunks . length = 0
0 commit comments