From e7d9cf21b638f3a92e9d666946f6330f7661285e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 16 Jun 2026 15:05:58 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20fix:=20Codex=20round=20?= =?UTF-8?q?5=20=E2=80=94=20refuse=20unresolvable=20resolves;=20expose=20pe?= =?UTF-8?q?nding=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two of three findings on c8abd826e1 (the third deferred to Slice B): - J3 resolve() refuses a requires_action job that has lost its pendingAction (e.g. a malformed record dropped on deserialize): it expires/finalizes the job instead of driving a resumed run with no reviewed interrupt payload — consistent with how active-listing + cleanup already treat a stale prompt. - J2 /chat/status returns the live pendingAction for a paused stream, so a client rebuilding from status (reload / cross-replica) has the action id + payload to render and submit the prompt, not just "paused". Deferred (Slice B): J1 — emitting a terminal SSE event on approval expiry so already-subscribed clients close. The store-level lifecycle can't emit transport events, and there are no live SSE subscribers to a paused stream until the Slice B runtime wiring exists; tracked for that work. tsc + lint clean; policy + type-contract specs pass. --- api/server/routes/agents/index.js | 4 ++++ packages/api/src/stream/ApprovalLifecycle.ts | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/api/server/routes/agents/index.js b/api/server/routes/agents/index.js index f634da0d16..9a6c6bde6e 100644 --- a/api/server/routes/agents/index.js +++ b/api/server/routes/agents/index.js @@ -218,6 +218,10 @@ router.get('/chat/status/:conversationId', async (req, res) => { aggregatedContent: resumeState?.aggregatedContent ?? [], createdAt: job.createdAt, resumeState, + // Surface the live pending approval so a client rebuilding from /chat/status + // (reload / cross-replica) has the action id + payload to render and submit + // the prompt, not just the knowledge that the stream is paused. + pendingAction: job.status === 'requires_action' && pendingLive ? pendingAction : undefined, }); }); diff --git a/packages/api/src/stream/ApprovalLifecycle.ts b/packages/api/src/stream/ApprovalLifecycle.ts index 1daff23c85..56fe00d859 100644 --- a/packages/api/src/stream/ApprovalLifecycle.ts +++ b/packages/api/src/stream/ApprovalLifecycle.ts @@ -77,6 +77,14 @@ export class ApprovalLifecycle { */ async resolve(streamId: string, expectedActionId?: string): Promise { const job = await this.store.getJob(streamId); + if (job?.status === 'requires_action' && !job.pendingAction) { + // The prompt was lost (e.g. a malformed record dropped on deserialize). + // It can't be reviewed, so finalize the job instead of driving a resumed + // run with no reviewed interrupt payload — consistent with how the active + // listing and cleanup treat a stale pending action. + await this.expire(streamId); + return false; + } if ( job?.status === 'requires_action' && job.pendingAction &&