@@ -1225,6 +1225,47 @@ describe("runAgentInSandbox — composite orchestration", () => {
12251225 onSpy . mockRestore ( ) ;
12261226 } ) ;
12271227
1228+ it ( "registers SIGTERM handler before any await in composite pre-work" , async ( ) => {
1229+ // In composite mode, sandbox.ts runs `await instance.commands.run("rm -f
1230+ // /tmp/sandcaster-ipc-*.json*")` to clear stale IPC files. If SIGTERM
1231+ // arrives during that await and no handler is registered, Node exits
1232+ // immediately and the sandbox leaks. The handler must be installed
1233+ // before the first awaited operation following sandbox creation.
1234+ const instance = makeCompositeInstance ( [ ] ) ;
1235+ registerFakeProvider ( instance ) ;
1236+
1237+ const onSpy = vi . spyOn ( process , "once" ) ;
1238+
1239+ for await ( const _ of runAgentInSandbox ( {
1240+ request : makeRequest ( { composite : { maxSandboxes : 2 } } ) ,
1241+ } ) ) {
1242+ // consume
1243+ }
1244+
1245+ const sigtermCall = onSpy . mock . calls . find ( ( call ) => call [ 0 ] === "SIGTERM" ) ;
1246+ expect ( sigtermCall ) . toBeDefined ( ) ;
1247+
1248+ // Find the IPC cleanup call to instance.commands.run.
1249+ const ipcCleanupCall = instance . commands . run . mock . calls . find (
1250+ ( call ) =>
1251+ typeof call [ 0 ] === "string" && call [ 0 ] . includes ( "sandcaster-ipc-" ) ,
1252+ ) ;
1253+ expect ( ipcCleanupCall ) . toBeDefined ( ) ;
1254+
1255+ const sigtermOrder = onSpy . mock . invocationCallOrder [
1256+ onSpy . mock . calls . findIndex ( ( c ) => c [ 0 ] === "SIGTERM" )
1257+ ] as number ;
1258+ const ipcCleanupOrder = instance . commands . run . mock . invocationCallOrder [
1259+ instance . commands . run . mock . calls . findIndex (
1260+ ( c ) => typeof c [ 0 ] === "string" && c [ 0 ] . includes ( "sandcaster-ipc-" ) ,
1261+ )
1262+ ] as number ;
1263+
1264+ expect ( sigtermOrder ) . toBeLessThan ( ipcCleanupOrder ) ;
1265+
1266+ onSpy . mockRestore ( ) ;
1267+ } ) ;
1268+
12281269 it ( "registers a SIGTERM handler that kills the instance in non-composite mode" , async ( ) => {
12291270 // Without a SIGTERM handler, Node exits immediately on signal and the
12301271 // `finally` block that calls `instance.kill()` never runs, leaving the
0 commit comments