@@ -27,6 +27,7 @@ import { CODEX_GPT5_BEHAVIOR_CONTRACT } from "../../prompt-overlay.js";
2727import { defaultCodexAppInventoryCache } from "./app-inventory-cache.js" ;
2828import * as authBridge from "./auth-bridge.js" ;
2929import { resolveCodexAppServerEnvApiKeyCacheKey } from "./auth-bridge.js" ;
30+ import { CodexAppServerRpcError } from "./client.js" ;
3031import { readCodexPluginConfig , resolveCodexAppServerRuntimeOptions } from "./config.js" ;
3132import {
3233 CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE ,
@@ -3498,7 +3499,10 @@ describe("runCodexAppServerAttempt", () => {
34983499 const c = {
34993500 request : vi . fn ( async ( method : string ) => {
35003501 if ( method === "thread/start" ) {
3501- throw new Error ( "401 authentication_error: Invalid bearer token" ) ;
3502+ throw new CodexAppServerRpcError (
3503+ { message : "401 authentication_error: Invalid bearer token" } ,
3504+ "thread/start" ,
3505+ ) ;
35023506 }
35033507 return { } ;
35043508 } ) ,
@@ -3553,6 +3557,36 @@ describe("runCodexAppServerAttempt", () => {
35533557 clearSpy . mockRestore ( ) ;
35543558 } ) ;
35553559
3560+ it ( "retires the shared Codex client when a spawned helper hits a thread/start write failure" , async ( ) => {
3561+ const clearSpy = vi . spyOn ( sharedClientModule , "clearSharedCodexAppServerClientIfCurrent" ) ;
3562+ clearSpy . mockClear ( ) ;
3563+ let failedClient : unknown ;
3564+ setCodexAppServerClientFactoryForTest ( async ( ) => {
3565+ const c = {
3566+ request : vi . fn ( async ( method : string ) => {
3567+ if ( method === "thread/start" ) {
3568+ throw new Error ( "write EPIPE" ) ;
3569+ }
3570+ return { } ;
3571+ } ) ,
3572+ addNotificationHandler : vi . fn ( ( ) => ( ) => undefined ) ,
3573+ addRequestHandler : vi . fn ( ( ) => ( ) => undefined ) ,
3574+ } ;
3575+ failedClient = c ;
3576+ return c as never ;
3577+ } ) ;
3578+ const params = createParams (
3579+ path . join ( tempDir , "session.jsonl" ) ,
3580+ path . join ( tempDir , "workspace" ) ,
3581+ ) ;
3582+ params . spawnedBy = "agent:main:session-parent" ;
3583+
3584+ await expect ( runCodexAppServerAttempt ( params ) ) . rejects . toThrow ( "write EPIPE" ) ;
3585+ const calledWithFailedClient = clearSpy . mock . calls . some ( ( [ arg ] ) => arg === failedClient ) ;
3586+ expect ( calledWithFailedClient ) . toBe ( true ) ;
3587+ clearSpy . mockRestore ( ) ;
3588+ } ) ;
3589+
35563590 it ( "retires the shared Codex client when a top-level run fails with a logical thread/start error" , async ( ) => {
35573591 const clearSpy = vi . spyOn ( sharedClientModule , "clearSharedCodexAppServerClientIfCurrent" ) ;
35583592 clearSpy . mockClear ( ) ;
@@ -3561,7 +3595,10 @@ describe("runCodexAppServerAttempt", () => {
35613595 const c = {
35623596 request : vi . fn ( async ( method : string ) => {
35633597 if ( method === "thread/start" ) {
3564- throw new Error ( "401 authentication_error: Invalid bearer token" ) ;
3598+ throw new CodexAppServerRpcError (
3599+ { message : "401 authentication_error: Invalid bearer token" } ,
3600+ "thread/start" ,
3601+ ) ;
35653602 }
35663603 return { } ;
35673604 } ) ,
0 commit comments