@@ -871,6 +871,147 @@ describe("ProviderCommandReactor", () => {
871871 } ) ;
872872 } ) ;
873873
874+ it ( "does not resume settled turns from stale recovery bindings" , async ( ) => {
875+ const modelSelection : ModelSelection = {
876+ instanceId : ProviderInstanceId . make ( "codex" ) ,
877+ model : "gpt-5-codex" ,
878+ } ;
879+ const markerThreadId = ThreadId . make ( "thread-1" ) ;
880+ const legacyThreadId = ThreadId . make ( "thread-settled-legacy" ) ;
881+ const markerTurnId = asTurnId ( "turn-settled-marker" ) ;
882+ const legacyTurnId = asTurnId ( "turn-settled-legacy" ) ;
883+ const harness = await createHarness ( {
884+ deferReactorStart : true ,
885+ providerBindings : [
886+ {
887+ threadId : markerThreadId ,
888+ provider : ProviderDriverKind . make ( "codex" ) ,
889+ providerInstanceId : ProviderInstanceId . make ( "codex" ) ,
890+ runtimeMode : "full-access" ,
891+ status : "stopped" ,
892+ resumeCursor : { threadId : "provider-thread-marker" } ,
893+ runtimePayload : {
894+ modelSelection,
895+ restartRecovery : makeProviderRestartRecoveryMarker ( {
896+ interruptedProviderTurnId : markerTurnId ,
897+ shutdownAt : "2026-01-01T00:00:02.000Z" ,
898+ } ) ,
899+ } ,
900+ lastSeenAt : "2026-01-01T00:00:02.000Z" ,
901+ } ,
902+ {
903+ threadId : legacyThreadId ,
904+ provider : ProviderDriverKind . make ( "codex" ) ,
905+ providerInstanceId : ProviderInstanceId . make ( "codex" ) ,
906+ runtimeMode : "full-access" ,
907+ status : "running" ,
908+ resumeCursor : { threadId : "provider-thread-legacy" } ,
909+ runtimePayload : {
910+ modelSelection,
911+ activeTurnId : legacyTurnId ,
912+ } ,
913+ lastSeenAt : "2026-01-01T00:00:02.000Z" ,
914+ } ,
915+ ] ,
916+ } ) ;
917+ await Effect . runPromise (
918+ harness . engine . dispatch ( {
919+ type : "thread.create" ,
920+ commandId : CommandId . make ( "cmd-create-settled-legacy-thread" ) ,
921+ threadId : legacyThreadId ,
922+ projectId : asProjectId ( "project-1" ) ,
923+ title : "Settled legacy thread" ,
924+ modelSelection,
925+ interactionMode : "default" ,
926+ runtimeMode : "full-access" ,
927+ branch : null ,
928+ worktreePath : null ,
929+ createdAt : "2026-01-01T00:00:00.000Z" ,
930+ } ) ,
931+ ) ;
932+
933+ const settleTurn = async ( threadId : ThreadId , turnId : TurnId , suffix : string ) => {
934+ await Effect . runPromise (
935+ harness . engine . dispatch ( {
936+ type : "thread.turn.start" ,
937+ commandId : CommandId . make ( `cmd-settled-turn-${ suffix } ` ) ,
938+ threadId,
939+ message : {
940+ messageId : asMessageId ( `message-settled-${ suffix } ` ) ,
941+ role : "user" ,
942+ text : "already finished" ,
943+ attachments : [ ] ,
944+ } ,
945+ modelSelection,
946+ interactionMode : "default" ,
947+ runtimeMode : "full-access" ,
948+ createdAt : "2026-01-01T00:00:00.500Z" ,
949+ } ) ,
950+ ) ;
951+ await Effect . runPromise (
952+ harness . engine . dispatch ( {
953+ type : "thread.session.set" ,
954+ commandId : CommandId . make ( `server:settled-running-${ suffix } ` ) ,
955+ threadId,
956+ session : {
957+ threadId,
958+ status : "running" ,
959+ providerName : "codex" ,
960+ providerInstanceId : ProviderInstanceId . make ( "codex" ) ,
961+ runtimeMode : "full-access" ,
962+ activeTurnId : turnId ,
963+ lastError : null ,
964+ updatedAt : "2026-01-01T00:00:01.000Z" ,
965+ } ,
966+ createdAt : "2026-01-01T00:00:01.000Z" ,
967+ } ) ,
968+ ) ;
969+ await Effect . runPromise (
970+ harness . engine . dispatch ( {
971+ type : "thread.session.set" ,
972+ commandId : CommandId . make ( `server:settled-ready-${ suffix } ` ) ,
973+ threadId,
974+ session : {
975+ threadId,
976+ status : "ready" ,
977+ providerName : "codex" ,
978+ providerInstanceId : ProviderInstanceId . make ( "codex" ) ,
979+ runtimeMode : "full-access" ,
980+ activeTurnId : null ,
981+ lastError : null ,
982+ updatedAt : "2026-01-01T00:00:01.500Z" ,
983+ } ,
984+ createdAt : "2026-01-01T00:00:01.500Z" ,
985+ } ) ,
986+ ) ;
987+ } ;
988+
989+ await settleTurn ( markerThreadId , markerTurnId , "marker" ) ;
990+ await settleTurn ( legacyThreadId , legacyTurnId , "legacy" ) ;
991+ expect (
992+ ( await harness . readTurns ( markerThreadId ) ) . find ( ( turn ) => turn . turnId === markerTurnId ) ?. state ,
993+ ) . toBe ( "completed" ) ;
994+ expect (
995+ ( await harness . readTurns ( legacyThreadId ) ) . find ( ( turn ) => turn . turnId === legacyTurnId ) ?. state ,
996+ ) . toBe ( "completed" ) ;
997+
998+ await harness . startReactor ( ) ;
999+ await harness . drain ( ) ;
1000+
1001+ expect ( harness . startSession ) . not . toHaveBeenCalled ( ) ;
1002+ expect ( harness . sendTurn ) . not . toHaveBeenCalled ( ) ;
1003+ for ( const threadId of [ markerThreadId , legacyThreadId ] ) {
1004+ expect ( harness . providerBindings . get ( threadId ) ) . toMatchObject ( {
1005+ status : "stopped" ,
1006+ runtimePayload : {
1007+ activeTurnId : null ,
1008+ restartRecovery : null ,
1009+ lastRuntimeEvent : "provider.restartRecovery.skipped" ,
1010+ } ,
1011+ } ) ;
1012+ }
1013+ } ) ;
1014+
8741015 it ( "recovers across temporary-SQLite runtimes and does not repeat a completed recovery" , async ( ) => {
8751016 const baseDir = NodeFS . mkdtempSync (
8761017 NodePath . join ( NodeOS . tmpdir ( ) , "t3code-reactor-two-runtime-" ) ,
0 commit comments