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.
This commit is contained in:
paseo-bot[bot]
2026-10-30 06:03:39 +00:00
parent 6041e86dad
commit a02fa39125
2 changed files with 33 additions and 7 deletions
@@ -1861,7 +1861,7 @@ function readCodexSubAgentActivity(item: unknown): CodexSubAgentActivity | null
interface CodexCollabAgentUpdate {
threadId: string;
status: ToolCallTimelineItem["status"];
shutdown: boolean;
preservesTerminal: boolean;
reopen: boolean;
}
@@ -1888,11 +1888,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",
},
];
@@ -1913,7 +1910,7 @@ function resolveCodexCollabAgentUpdate(
): ToolCallTimelineItem["status"] {
if (
isTerminalSubAgentStatus(previous) &&
(update.shutdown || (update.status === "running" && !update.reopen))
(update.preservesTerminal || (update.status === "running" && !update.reopen))
) {
return previous;
}
@@ -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"],