@@ -302,7 +302,7 @@ describe("respawnGatewayProcessForUpdate", () => {
302302 "gateway" ,
303303 "run" ,
304304 ] ;
305- spawnMock . mockReturnValue ( { pid : 5151 , unref : vi . fn ( ) , kill : vi . fn ( ) } ) ;
305+ spawnMock . mockReturnValue ( { pid : 5151 , unref : vi . fn ( ) , kill : vi . fn ( ) , on : vi . fn ( ) } ) ;
306306
307307 const result = respawnGatewayProcessForUpdate ( ) ;
308308
@@ -329,7 +329,7 @@ describe("respawnGatewayProcessForUpdate", () => {
329329 "gateway" ,
330330 "run" ,
331331 ] ;
332- spawnMock . mockReturnValue ( { pid : 7171 , unref : vi . fn ( ) , kill : vi . fn ( ) } ) ;
332+ spawnMock . mockReturnValue ( { pid : 7171 , unref : vi . fn ( ) , kill : vi . fn ( ) , on : vi . fn ( ) } ) ;
333333
334334 const result = respawnGatewayProcessForUpdate ( ) ;
335335
@@ -352,7 +352,7 @@ describe("respawnGatewayProcessForUpdate", () => {
352352 const entry =
353353 "/app/node_modules/.pnpm/@[email protected] /node_modules/@anthropic/sdk/dist/index.js" ; 354354 process . argv = [ "/usr/local/bin/node" , entry , "gateway" , "run" ] ;
355- spawnMock . mockReturnValue ( { pid : 8181 , unref : vi . fn ( ) , kill : vi . fn ( ) } ) ;
355+ spawnMock . mockReturnValue ( { pid : 8181 , unref : vi . fn ( ) , kill : vi . fn ( ) , on : vi . fn ( ) } ) ;
356356
357357 respawnGatewayProcessForUpdate ( ) ;
358358
@@ -369,7 +369,7 @@ describe("respawnGatewayProcessForUpdate", () => {
369369 process . env . XPC_SERVICE_NAME = "ai.openclaw.mac" ;
370370 process . execArgv = [ ] ;
371371 process . argv = [ "/usr/local/bin/node" , "/repo/dist/index.js" , "gateway" , "run" ] ;
372- spawnMock . mockReturnValue ( { pid : 6161 , unref : vi . fn ( ) , kill : vi . fn ( ) } ) ;
372+ spawnMock . mockReturnValue ( { pid : 6161 , unref : vi . fn ( ) , kill : vi . fn ( ) , on : vi . fn ( ) } ) ;
373373
374374 const result = respawnGatewayProcessForUpdate ( ) ;
375375
@@ -413,4 +413,27 @@ describe("respawnGatewayProcessForUpdate", () => {
413413 expect ( result . mode ) . toBe ( "failed" ) ;
414414 expect ( result . detail ) . toContain ( "spawn failed" ) ;
415415 } ) ;
416+
417+ it ( "registers a no-op error listener on the detached child to prevent unhandled async errors" , ( ) => {
418+ delete process . env . OPENCLAW_NO_RESPAWN ;
419+ clearSupervisorHints ( ) ;
420+ setPlatform ( "linux" ) ;
421+
422+ const onMock = vi . fn ( ) ;
423+ spawnMock . mockReturnValue ( {
424+ pid : 9091 ,
425+ unref : vi . fn ( ) ,
426+ kill : vi . fn ( ) ,
427+ on : onMock ,
428+ } ) ;
429+
430+ const result = respawnGatewayProcessForUpdate ( ) ;
431+
432+ expect ( result . mode ) . toBe ( "spawned" ) ;
433+ expect ( onMock ) . toHaveBeenCalledWith ( "error" , expect . any ( Function ) ) ;
434+
435+ const errorHandler = onMock . mock . calls [ 0 ] ?. [ 1 ] as ( ...args : unknown [ ] ) => void ;
436+ expect ( ( ) => errorHandler ( new Error ( "ENOMEM" ) ) ) . not . toThrow ( ) ;
437+ expect ( ( ) => errorHandler ( new Error ( "spawn EBADF" ) ) ) . not . toThrow ( ) ;
438+ } ) ;
416439} ) ;
0 commit comments