@@ -448,6 +448,103 @@ test('runAgentForChat clears a stale codex-app thread binding and retries once',
448448 ) ;
449449} ) ;
450450
451+ test ( 'runAgentForChat clears codex-app binding after configuration permission errors' , async ( ) => {
452+ const calls = [ ] ;
453+ const restarts = [ ] ;
454+ const { runner, threads, persistedThreadSnapshots } = buildRunner ( {
455+ getGlobalAgent : ( ) => 'codex-app' ,
456+ resolveEffectiveAgentId : ( _chatId , _topicId , overrideAgentId ) =>
457+ overrideAgentId || 'codex-app' ,
458+ resolveThreadId : ( _threads , chatId , topicId , agentId ) => ( {
459+ threadKey : `${ chatId } :${ topicId || 'root' } :${ agentId } ` ,
460+ threadId : threads . get ( `${ chatId } :${ topicId || 'root' } :${ agentId } ` ) ,
461+ migrated : false ,
462+ } ) ,
463+ runSessionBackedChatTurn : async ( options ) => {
464+ calls . push ( options ) ;
465+ if ( calls . length === 1 ) {
466+ throw new Error (
467+ 'failed to load configuration: Operation not permitted (os error 1)'
468+ ) ;
469+ }
470+ return {
471+ text : 'respuesta recuperada' ,
472+ threadId : 'fresh-config-thread' ,
473+ turnId : 'turn-fresh' ,
474+ } ;
475+ } ,
476+ restartSessionBackedServer : async ( options ) => {
477+ restarts . push ( options ) ;
478+ } ,
479+ } ) ;
480+
481+ threads . set ( '31:root:codex-app' , 'permission-thread' ) ;
482+ const response = await runner . runAgentForChat ( 31 , 'recupera esto' , {
483+ agentId : 'codex-app' ,
484+ } ) ;
485+
486+ assert . equal ( response , 'respuesta recuperada' ) ;
487+ assert . equal ( calls . length , 2 ) ;
488+ assert . deepEqual ( restarts , [ { agentId : 'codex-app' } ] ) ;
489+ assert . equal ( calls [ 0 ] . threadId , 'permission-thread' ) ;
490+ assert . equal ( calls [ 1 ] . threadId , undefined ) ;
491+ assert . equal ( threads . get ( '31:root:codex-app' ) , 'fresh-config-thread' ) ;
492+ assert . equal ( persistedThreadSnapshots . length , 2 ) ;
493+ assert . equal (
494+ persistedThreadSnapshots [ 0 ] . has ( '31:root:codex-app' ) ,
495+ false
496+ ) ;
497+ } ) ;
498+
499+ test ( 'runAgentForChat clears archived codex-app thread bindings and retries once' , async ( ) => {
500+ const calls = [ ] ;
501+ const restarts = [ ] ;
502+ const { runner, threads, persistedThreadSnapshots } = buildRunner ( {
503+ getGlobalAgent : ( ) => 'codex-app' ,
504+ resolveEffectiveAgentId : ( _chatId , _topicId , overrideAgentId ) =>
505+ overrideAgentId || 'codex-app' ,
506+ resolveThreadId : ( _threads , chatId , topicId , agentId ) => ( {
507+ threadKey : `${ chatId } :${ topicId || 'root' } :${ agentId } ` ,
508+ threadId : threads . get ( `${ chatId } :${ topicId || 'root' } :${ agentId } ` ) ,
509+ migrated : false ,
510+ } ) ,
511+ runSessionBackedChatTurn : async ( options ) => {
512+ calls . push ( options ) ;
513+ if ( calls . length === 1 ) {
514+ throw new Error (
515+ 'session archived-thread is archived. Run `codex unarchive archived-thread` to unarchive it first.'
516+ ) ;
517+ }
518+ return {
519+ text : 'respuesta tras archivar' ,
520+ threadId : 'fresh-archived-thread' ,
521+ turnId : 'turn-fresh' ,
522+ } ;
523+ } ,
524+ restartSessionBackedServer : async ( options ) => {
525+ restarts . push ( options ) ;
526+ } ,
527+ } ) ;
528+
529+ threads . set ( '42:6484:codex-app' , 'archived-thread' ) ;
530+ const response = await runner . runAgentForChat ( 42 , 'reserva' , {
531+ agentId : 'codex-app' ,
532+ topicId : 6484 ,
533+ } ) ;
534+
535+ assert . equal ( response , 'respuesta tras archivar' ) ;
536+ assert . equal ( calls . length , 2 ) ;
537+ assert . deepEqual ( restarts , [ { agentId : 'codex-app' } ] ) ;
538+ assert . equal ( calls [ 0 ] . threadId , 'archived-thread' ) ;
539+ assert . equal ( calls [ 1 ] . threadId , undefined ) ;
540+ assert . equal ( threads . get ( '42:6484:codex-app' ) , 'fresh-archived-thread' ) ;
541+ assert . equal ( persistedThreadSnapshots . length , 2 ) ;
542+ assert . equal (
543+ persistedThreadSnapshots [ 0 ] . has ( '42:6484:codex-app' ) ,
544+ false
545+ ) ;
546+ } ) ;
547+
451548test ( 'runAgentForChat assigns a title when a new codex-app thread is created' , async ( ) => {
452549 const titleCalls = [ ] ;
453550 const { runner } = buildRunner ( {
0 commit comments