fix: prevent stale codex processing banner

Codex can finish a turn and clear the composer banner while delayed status
checks or parent processing state still believe the session is running.
That leaves a stale path that can re-open the banner after the response
is already complete.

This was visible with Codex responses that render a delayed remote image.
The image load/reflow gives late session-status handling a chance to
reassert processing. A simple text-only prompt did not create the same
render window.

Reproduces with Codex using:

Reply with exactly the following Markdown, no code fence, no explanation:

Start

![scroll reflow test](https://picsum.photos/seed/pr788-scroll/1600/1400)

BOTTOM_SENTINEL_PR_788

Did not reproduce with: hi

The fix marks Codex terminal turns complete before status probes can
report them active, ignores stale processing status after a terminal
event, and only restores local loading when a session newly enters
processing.
This commit is contained in:
Haileyesus
2026-06-08 20:27:14 +03:00
parent f4a1614a0a
commit 0299beccb9
3 changed files with 51 additions and 1 deletions

View File

@@ -279,6 +279,16 @@ export async function queryCodex(command, options = {}, ws) {
startedAt: new Date().toISOString()
});
};
const markSessionFinished = (id) => {
if (!id) {
return;
}
const session = activeCodexSessions.get(id);
if (session && session.status !== 'aborted') {
session.status = 'completed';
}
};
// Existing sessions can be tracked immediately; new sessions are tracked after thread.started.
if (capturedSessionId) {
@@ -324,6 +334,10 @@ export async function queryCodex(command, options = {}, ws) {
continue;
}
if (event.type === 'turn.completed' || event.type === 'turn.failed') {
markSessionFinished(capturedSessionId || sessionId);
}
const transformed = transformCodexEvent(event);
// Normalize the transformed event into NormalizedMessage(s) via adapter
@@ -354,6 +368,8 @@ export async function queryCodex(command, options = {}, ws) {
// Send completion event
if (!terminalFailure) {
markSessionFinished(capturedSessionId || sessionId);
sendMessage(ws, createNormalizedMessage({
kind: 'complete',
actualSessionId: capturedSessionId || thread.id || sessionId || null,