Skip to content

Commit 9dee38d

Browse files
Handle retryable turn errors as session status (#289)
1 parent 8812d4d commit 9dee38d

2 files changed

Lines changed: 58 additions & 10 deletions

File tree

src/CodexEventHandler.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,15 @@ export class CodexEventHandler {
566566

567567
private async createErrorEvent(params: ErrorNotification): Promise<UpdateSessionEvent> {
568568
const error = params.error.codexErrorInfo;
569-
if (error === "usageLimitExceeded") {
569+
if (params.willRetry) {
570+
return this.createCodexSessionInfoUpdate({
571+
error: {
572+
...params.error,
573+
turnId: params.turnId,
574+
willRetry: true,
575+
},
576+
});
577+
} else if (error === "usageLimitExceeded") {
570578
this.failure = RequestError.internalError(
571579
this.createTurnErrorData(params.error),
572580
);

src/__tests__/CodexACPAgent/auth-error-events.test.ts

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,45 @@ const configuredAuthFailureCases: Array<{
6464
];
6565

6666
describe("CodexEventHandler - auth error events", () => {
67+
it("keeps the prompt alive for a retryable HTTP 401", async () => {
68+
const {result: response, updates} = await runPromptWithError(createTestSessionState({
69+
sessionId: "retrying-session",
70+
account: { type: "apiKey" },
71+
}), {
72+
message: "Reconnecting after provider returned 401",
73+
codexErrorInfo: {
74+
responseStreamDisconnected: {
75+
httpStatusCode: 401,
76+
},
77+
},
78+
additionalDetails: "HTTP status 401",
79+
}, true);
80+
81+
expect(response).toMatchObject({
82+
stopReason: "end_turn",
83+
});
84+
expect(updates).toEqual([{
85+
sessionUpdate: "session_info_update",
86+
_meta: {
87+
codex: {
88+
error: {
89+
message: "Reconnecting after provider returned 401",
90+
codexErrorInfo: {
91+
responseStreamDisconnected: {
92+
httpStatusCode: 401,
93+
},
94+
},
95+
additionalDetails: "HTTP status 401",
96+
turnId: "turn-id",
97+
willRetry: true,
98+
},
99+
},
100+
},
101+
}]);
102+
});
103+
67104
it("returns AuthRequired for auth errors when no auth is configured", async () => {
68-
const error = await runPromptWithError(createTestSessionState({
105+
const {result: error} = await runPromptWithError(createTestSessionState({
69106
sessionId: "unauthenticated-session",
70107
account: null,
71108
authConfigured: false,
@@ -88,7 +125,7 @@ describe("CodexEventHandler - auth error events", () => {
88125
it.each(configuredAuthFailureCases)(
89126
"returns InternalError with details for $name when auth is configured",
90127
async ({turnError, sessionOverrides, expectedData}) => {
91-
const error = await runPromptWithError(createTestSessionState({
128+
const {result: error} = await runPromptWithError(createTestSessionState({
92129
sessionId: "authenticated-session",
93130
account: { type: "apiKey" },
94131
...sessionOverrides,
@@ -109,7 +146,8 @@ describe("CodexEventHandler - auth error events", () => {
109146
async function runPromptWithError(
110147
sessionState: SessionState,
111148
turnError: ErrorNotification["error"],
112-
): Promise<unknown> {
149+
willRetry = false,
150+
): Promise<{result: unknown; updates: unknown[]}> {
113151
const mockFixture = createCodexMockTestFixture();
114152
const codexAcpAgent = mockFixture.getCodexAcpAgent();
115153
const codexAppServerClient = mockFixture.getCodexAppServerClient();
@@ -134,7 +172,7 @@ async function runPromptWithError(
134172
params: {
135173
threadId: sessionState.sessionId,
136174
turnId: "turn-id",
137-
willRetry: false,
175+
willRetry,
138176
error: turnError,
139177
},
140178
});
@@ -144,14 +182,16 @@ async function runPromptWithError(
144182
turn: createTurn("completed"),
145183
});
146184

147-
let caughtError: unknown;
185+
let result: unknown;
148186
try {
149-
await promptPromise;
187+
result = await promptPromise;
150188
} catch (error) {
151-
caughtError = error;
189+
result = error;
152190
}
153-
expect(caughtError).toBeDefined();
154-
return caughtError;
191+
return {
192+
result,
193+
updates: mockFixture.getAcpConnectionEvents([]).map(event => event.args[0].update),
194+
};
155195
}
156196

157197
function createTurn(status: "inProgress" | "completed") {

0 commit comments

Comments
 (0)