@@ -762,6 +762,67 @@ describe("subagent registry lifecycle hardening", () => {
762762 } ,
763763 ) ;
764764
765+ it ( "rejects a yield after direct delete cleanup has been dispatched" , async ( ) => {
766+ const entry = createRunEntry ( { cleanup : "delete" , expectsCompletionMessage : false } ) ;
767+ const runs = new Map ( [ [ entry . runId , entry ] ] ) ;
768+ let releaseDelete : ( ( ) => void ) | undefined ;
769+ gatewayMocks . callGateway . mockImplementation ( ( opts ) => {
770+ if ( opts . method !== "sessions.delete" ) {
771+ return Promise . resolve ( { } ) ;
772+ }
773+ return new Promise < Record < string , unknown > > ( ( resolve ) => {
774+ releaseDelete = ( ) => resolve ( { } ) ;
775+ } ) ;
776+ } ) ;
777+ const controller = createLifecycleController ( { entry, runs } ) ;
778+
779+ await controller . completeSubagentRun ( {
780+ runId : entry . runId ,
781+ endedAt : 4_000 ,
782+ outcome : { status : "ok" } ,
783+ reason : SUBAGENT_ENDED_REASON_COMPLETE ,
784+ triggerCleanup : true ,
785+ } ) ;
786+ await vi . waitFor ( ( ) => expect ( entry . deleteCleanupDispatchedAt ) . toBeTypeOf ( "number" ) ) ;
787+
788+ expect ( markSubagentRunPausedAfterYield ( { entry, endedAt : 4_001 } ) ) . toBe ( false ) ;
789+ expect ( entry . pauseReason ) . toBeUndefined ( ) ;
790+ expect ( entry . endedReason ) . toBe ( SUBAGENT_ENDED_REASON_COMPLETE ) ;
791+
792+ releaseDelete ?.( ) ;
793+ await vi . waitFor ( ( ) => expect ( runs . has ( entry . runId ) ) . toBe ( false ) ) ;
794+ } ) ;
795+
796+ it ( "rejects a yield after announce cleanup hands off delete dispatch" , async ( ) => {
797+ const entry = createRunEntry ( { cleanup : "delete" , expectsCompletionMessage : true } ) ;
798+ const runs = new Map ( [ [ entry . runId , entry ] ] ) ;
799+ let releaseAnnounce : ( ( ) => void ) | undefined ;
800+ const runSubagentAnnounceFlow : LifecycleControllerParams [ "runSubagentAnnounceFlow" ] = vi . fn (
801+ ( announceParams ) =>
802+ new Promise < boolean > ( ( resolve ) => {
803+ expect ( announceParams . onBeforeDeleteChildSession ?.( ) ) . toBe ( true ) ;
804+ releaseAnnounce = ( ) => resolve ( true ) ;
805+ } ) ,
806+ ) ;
807+ const controller = createLifecycleController ( { entry, runs, runSubagentAnnounceFlow } ) ;
808+
809+ await controller . completeSubagentRun ( {
810+ runId : entry . runId ,
811+ endedAt : 4_000 ,
812+ outcome : { status : "ok" } ,
813+ reason : SUBAGENT_ENDED_REASON_COMPLETE ,
814+ triggerCleanup : true ,
815+ } ) ;
816+ await vi . waitFor ( ( ) => expect ( entry . deleteCleanupDispatchedAt ) . toBeTypeOf ( "number" ) ) ;
817+
818+ expect ( markSubagentRunPausedAfterYield ( { entry, endedAt : 4_001 } ) ) . toBe ( false ) ;
819+ expect ( entry . pauseReason ) . toBeUndefined ( ) ;
820+ expect ( entry . endedReason ) . toBe ( SUBAGENT_ENDED_REASON_COMPLETE ) ;
821+
822+ releaseAnnounce ?.( ) ;
823+ await vi . waitFor ( ( ) => expect ( runs . has ( entry . runId ) ) . toBe ( false ) ) ;
824+ } ) ;
825+
765826 it ( "discards completion capture when an authoritative yield arrives during the await" , async ( ) => {
766827 const entry = createRunEntry ( { expectsCompletionMessage : true } ) ;
767828 let finishCapture : ( ( result : string ) => void ) | undefined ;
0 commit comments