From d1e20d2f29a69efd2fc721bc8d27c7d6d5959fa4 Mon Sep 17 00:00:00 2001 From: litang9 <141409885+litang9@users.noreply.github.com> Date: Wed, 17 Jun 2026 01:20:34 +0800 Subject: [PATCH] fix(gateway): surface codex app-server returned failures (#93665) * fix(gateway): surface codex app-server returned failures * fix(auto-reply): retain codex app-server failures * fix(agents): mark codex completion timeouts terminal --------- Co-authored-by: Alex Tang Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com> --- .../run.codex-app-server-recovery.test.ts | 16 ++++++++++++++++ src/agents/embedded-agent-runner/run.ts | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts b/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts index 4708c42a0767..2bab016422bb 100644 --- a/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts +++ b/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts @@ -235,6 +235,11 @@ describe("runEmbeddedAgent Codex app-server recovery", () => { }); expect(result.meta.timeoutPhase).toBe("provider"); expect(result.meta.providerStarted).toBe(true); + expect(result.meta.error).toEqual({ + kind: "incomplete_turn", + message: CODEX_MISSING_TERMINAL_MESSAGE, + fallbackSafe: false, + }); expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2); expect(mockedMarkAuthProfileFailure).not.toHaveBeenCalled(); }); @@ -264,6 +269,11 @@ describe("runEmbeddedAgent Codex app-server recovery", () => { isError: true, text: CODEX_MISSING_TERMINAL_MESSAGE, }); + expect(result.meta.error).toEqual({ + kind: "incomplete_turn", + message: CODEX_MISSING_TERMINAL_MESSAGE, + fallbackSafe: false, + }); expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1); expect(mockedMarkAuthProfileFailure).not.toHaveBeenCalled(); }); @@ -316,6 +326,12 @@ describe("runEmbeddedAgent Codex app-server recovery", () => { }); expect(result.meta.replayInvalid).toBe(true); expect(result.meta.livenessState).toBe("abandoned"); + expect(result.meta.error).toEqual({ + kind: "incomplete_turn", + message: + "Codex stopped before confirming the turn was complete. Some work may already have been performed; verify the current state before retrying.", + fallbackSafe: false, + }); expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1); expect(mockedMarkAuthProfileFailure).not.toHaveBeenCalled(); }); diff --git a/src/agents/embedded-agent-runner/run.ts b/src/agents/embedded-agent-runner/run.ts index 62178030c1be..100a9474063f 100644 --- a/src/agents/embedded-agent-runner/run.ts +++ b/src/agents/embedded-agent-runner/run.ts @@ -3377,6 +3377,17 @@ async function runEmbeddedAgentInternal( livenessState, timeoutPhase, providerStarted, + // Completion-idle recovery is exhausted here. Keep this terminal so + // model fallback cannot replay a potentially still-active Codex turn. + ...(shouldSurfaceCodexCompletionTimeout + ? { + error: { + kind: "incomplete_turn" as const, + message: timeoutText, + fallbackSafe: false, + }, + } + : {}), toolSummary: attemptToolSummary, ...(failureSignal ? { failureSignal } : {}), agentHarnessResultClassification: attempt.agentHarnessResultClassification,