@@ -211,6 +211,24 @@ export const sseHandlers: Record<string, SSEHandler> = {
211211 options . timeout || STREAM_TIMEOUT_MS ,
212212 options . abortSignal
213213 )
214+ if ( completion ?. status === 'background' ) {
215+ toolCall . status = 'skipped'
216+ toolCall . endTime = Date . now ( )
217+ markToolComplete (
218+ toolCall . id ,
219+ toolCall . name ,
220+ 202 ,
221+ completion . message || 'Tool execution moved to background' ,
222+ { background : true }
223+ ) . catch ( ( err ) => {
224+ logger . error ( 'markToolComplete fire-and-forget failed (run tool background)' , {
225+ toolCallId : toolCall . id ,
226+ error : err instanceof Error ? err . message : String ( err ) ,
227+ } )
228+ } )
229+ markToolResultSeen ( toolCallId )
230+ return
231+ }
214232 const success = completion ?. status === 'success'
215233 toolCall . status = success ? 'success' : 'error'
216234 toolCall . endTime = Date . now ( )
@@ -235,48 +253,40 @@ export const sseHandlers: Record<string, SSEHandler> = {
235253 if ( decision ?. status === 'rejected' || decision ?. status === 'error' ) {
236254 toolCall . status = 'rejected'
237255 toolCall . endTime = Date . now ( )
238- await markToolComplete (
256+ // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport
257+ markToolComplete (
239258 toolCall . id ,
240259 toolCall . name ,
241260 400 ,
242261 decision . message || 'Tool execution rejected' ,
243262 { skipped : true , reason : 'user_rejected' }
244- )
245- markToolResultSeen ( toolCall . id )
246- await options . onEvent ?.( {
247- type : 'tool_result' ,
248- toolCallId : toolCall . id ,
249- data : {
250- id : toolCall . id ,
251- name : toolCall . name ,
252- success : false ,
253- result : { skipped : true , reason : 'user_rejected' } ,
254- } ,
263+ ) . catch ( ( err ) => {
264+ logger . error ( 'markToolComplete fire-and-forget failed (rejected)' , {
265+ toolCallId : toolCall . id ,
266+ error : err instanceof Error ? err . message : String ( err ) ,
267+ } )
255268 } )
269+ markToolResultSeen ( toolCall . id )
256270 return
257271 }
258272
259273 if ( decision ?. status === 'background' ) {
260274 toolCall . status = 'skipped'
261275 toolCall . endTime = Date . now ( )
262- await markToolComplete (
276+ // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport
277+ markToolComplete (
263278 toolCall . id ,
264279 toolCall . name ,
265280 202 ,
266281 decision . message || 'Tool execution moved to background' ,
267282 { background : true }
268- )
269- markToolResultSeen ( toolCall . id )
270- await options . onEvent ?.( {
271- type : 'tool_result' ,
272- toolCallId : toolCall . id ,
273- data : {
274- id : toolCall . id ,
275- name : toolCall . name ,
276- success : true ,
277- result : { background : true } ,
278- } ,
283+ ) . catch ( ( err ) => {
284+ logger . error ( 'markToolComplete fire-and-forget failed (background)' , {
285+ toolCallId : toolCall . id ,
286+ error : err instanceof Error ? err . message : String ( err ) ,
287+ } )
279288 } )
289+ markToolResultSeen ( toolCall . id )
280290 return
281291 }
282292 }
@@ -436,47 +446,39 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
436446 if ( decision ?. status === 'rejected' || decision ?. status === 'error' ) {
437447 toolCall . status = 'rejected'
438448 toolCall . endTime = Date . now ( )
439- await markToolComplete (
449+ // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport
450+ markToolComplete (
440451 toolCall . id ,
441452 toolCall . name ,
442453 400 ,
443454 decision . message || 'Tool execution rejected' ,
444455 { skipped : true , reason : 'user_rejected' }
445- )
446- markToolResultSeen ( toolCall . id )
447- await options ?. onEvent ?.( {
448- type : 'tool_result' ,
449- toolCallId : toolCall . id ,
450- data : {
451- id : toolCall . id ,
452- name : toolCall . name ,
453- success : false ,
454- result : { skipped : true , reason : 'user_rejected' } ,
455- } ,
456+ ) . catch ( ( err ) => {
457+ logger . error ( 'markToolComplete fire-and-forget failed (subagent rejected)' , {
458+ toolCallId : toolCall . id ,
459+ error : err instanceof Error ? err . message : String ( err ) ,
460+ } )
456461 } )
462+ markToolResultSeen ( toolCall . id )
457463 return
458464 }
459465 if ( decision ?. status === 'background' ) {
460466 toolCall . status = 'skipped'
461467 toolCall . endTime = Date . now ( )
462- await markToolComplete (
468+ // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport
469+ markToolComplete (
463470 toolCall . id ,
464471 toolCall . name ,
465472 202 ,
466473 decision . message || 'Tool execution moved to background' ,
467474 { background : true }
468- )
469- markToolResultSeen ( toolCall . id )
470- await options ?. onEvent ?.( {
471- type : 'tool_result' ,
472- toolCallId : toolCall . id ,
473- data : {
474- id : toolCall . id ,
475- name : toolCall . name ,
476- success : true ,
477- result : { background : true } ,
478- } ,
475+ ) . catch ( ( err ) => {
476+ logger . error ( 'markToolComplete fire-and-forget failed (subagent background)' , {
477+ toolCallId : toolCall . id ,
478+ error : err instanceof Error ? err . message : String ( err ) ,
479+ } )
479480 } )
481+ markToolResultSeen ( toolCall . id )
480482 return
481483 }
482484 }
@@ -507,6 +509,24 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
507509 markToolResultSeen ( toolCallId )
508510 return
509511 }
512+ if ( completion ?. status === 'background' ) {
513+ toolCall . status = 'skipped'
514+ toolCall . endTime = Date . now ( )
515+ markToolComplete (
516+ toolCall . id ,
517+ toolCall . name ,
518+ 202 ,
519+ completion . message || 'Tool execution moved to background' ,
520+ { background : true }
521+ ) . catch ( ( err ) => {
522+ logger . error ( 'markToolComplete fire-and-forget failed (subagent run tool background)' , {
523+ toolCallId : toolCall . id ,
524+ error : err instanceof Error ? err . message : String ( err ) ,
525+ } )
526+ } )
527+ markToolResultSeen ( toolCallId )
528+ return
529+ }
510530 const success = completion ?. status === 'success'
511531 toolCall . status = success ? 'success' : 'error'
512532 toolCall . endTime = Date . now ( )
0 commit comments