From c299598aea86886791d1dd9423a6e68ff37e43b9 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 04:54:17 +0000 Subject: [PATCH] fix(codex): settle closed children independently of prior errors Codex derives a close record's status from the child's previous state, so closing an errored child produces a failed record even after shutdown. Treat close as a terminal-preserving child event rather than a successful control-card status, retaining earlier completion when a close is stale. --- .../agent/providers/codex-app-server-agent.ts | 11 +++---- .../providers/codex/collab-replay.test.ts | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index 81c2e06b37..179e0db8c6 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -1864,7 +1864,7 @@ function readCodexSubAgentActivity(item: unknown): CodexSubAgentActivity | null interface CodexCollabAgentUpdate { threadId: string; status: ToolCallTimelineItem["status"]; - shutdown: boolean; + preservesTerminal: boolean; reopen: boolean; } @@ -1891,11 +1891,8 @@ function readCodexCollabAgentControl(item: unknown): CodexCollabAgentControl | n return [ { threadId, - status: - record.tool === "closeAgent" && record.status === "completed" && status === "running" - ? "canceled" - : status, - shutdown: state.status === "shutdown", + status: record.tool === "closeAgent" && status === "running" ? "canceled" : status, + preservesTerminal: state.status === "shutdown" || record.tool === "closeAgent", reopen: record.tool === "sendInput" && record.status === "completed", }, ]; @@ -1916,7 +1913,7 @@ function resolveCodexCollabAgentUpdate( ): ToolCallTimelineItem["status"] { if ( isTerminalSubAgentStatus(previous) && - (update.shutdown || (update.status === "running" && !update.reopen)) + (update.preservesTerminal || (update.status === "running" && !update.reopen)) ) { return previous; } diff --git a/packages/server/src/server/agent/providers/codex/collab-replay.test.ts b/packages/server/src/server/agent/providers/codex/collab-replay.test.ts index da86b1eb79..1773600bdb 100644 --- a/packages/server/src/server/agent/providers/codex/collab-replay.test.ts +++ b/packages/server/src/server/agent/providers/codex/collab-replay.test.ts @@ -86,6 +86,35 @@ describe("Codex collaboration history", () => { ).toEqual([{ id: "first", status: "canceled" }]); }); + test("stops counting an errored child whose close record reports failed", async () => { + const history = await replay([ + collab("spawnAgent", { first: "pendingInit" }, "spawn-first"), + { ...collab("closeAgent", { first: "errored" }), status: "failed" }, + ]); + expect( + history.flatMap((event) => + event.type === "provider_subagent" && event.event.type === "upsert" + ? [{ id: event.event.id, status: event.event.status }] + : [], + ), + ).toEqual([{ id: "first", status: "canceled" }]); + }); + + test("preserves completion when a close carries an older running snapshot", async () => { + const history = await replay([ + collab("spawnAgent", { first: "pendingInit" }, "spawn-first"), + collab("wait", { first: "completed" }), + collab("closeAgent", { first: "running" }), + ]); + expect( + history.flatMap((event) => + event.type === "provider_subagent" && event.event.type === "upsert" + ? [{ id: event.event.id, status: event.event.status }] + : [], + ), + ).toEqual([{ id: "first", status: "completed" }]); + }); + test.each([ ["shutdown", "canceled"], ["notFound", "failed"],