From cf3e714b0f58e29c8fa8660db50d2187e3263b65 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Mon, 5 Oct 2026 00:13:16 -0700 Subject: [PATCH] fix(server): Stop ends a Codex command after its thread was settled (#15546) Co-authored-by: Claude Opus 5.5 (1M context) --- .../Adapters/CodexAdapterV2.test.ts | 14 +++++++++++--- .../orchestration-v2/Adapters/CodexAdapterV2.ts | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 21b398cdc7ad353f413ccebed3014fa5bc09566e..f8b635a9fa9c47b7e2ea38e1268dc86addff08e1 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -3917,7 +3917,9 @@ describe("CodexAdapterV2 post-settle continuation", () => { ), ); - const backgroundStopCases = [true, false, "still_running"] as const; + // "thread_unloaded": the thread was settled, so T3 unsubscribed and Codex + // unloaded it (killing its terminals) before Stop arrived. + const backgroundStopCases = [true, false, "still_running", "thread_unloaded"] as const; const makeBackgroundStopTranscript = (terminated: (typeof backgroundStopCases)[number]) => { const stillRunning = terminated === "still_running"; return makeCodexReplayTranscript({ @@ -3936,9 +3938,15 @@ describe("CodexAdapterV2 post-settle continuation", () => { { type: "emit_inbound", label: "terminate-background-command", - frame: { id: 4, result: { terminated: terminated === true } }, + frame: + terminated === "thread_unloaded" + ? { + id: 4, + error: { code: -32600, message: `thread not found: ${BG_NATIVE_THREAD}` }, + } + : { id: 4, result: { terminated: terminated === true } }, }, - ...(terminated !== true + ...(terminated === false || stillRunning ? [ { type: "expect_outbound" as const, diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 2256f4ff9e9904e437a04eb2253c2d7b41f28e02..8dad9de3929f17651c56a51d9d1d23907cd7c790 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -2054,6 +2054,13 @@ export function makeCodexAdapterV2(adapterOptions: CodexAdapterV2Options): Provi Effect.catchTags({ CodexAppServerProcessExitedError: () => Effect.succeed({ terminated: true }), CodexAppServerInputStreamEndedError: () => Effect.succeed({ terminated: true }), + // The thread is unloaded, as Codex does a minute after a + // settle or archive unsubscribes it. Unloading kills the + // thread's terminals, so nothing is left to stop. + CodexAppServerRequestError: (error) => + error.code === -32600 && error.errorMessage.startsWith("thread not found:") + ? Effect.succeed({ terminated: true }) + : Effect.fail(error), }), ); const result = yield* decodeCodexBackgroundTerminalTerminateResponse(response); -- 2.54.0