Codex Desktop: background turn reported completed/interrupted while JSONL continues without final_answer or task_complete
Summary
Codex Desktop/app-server can report a background-created turn as completed/interrupted while the underlying rollout JSONL is still receiving events and has no final_answer or task_complete.
This appears to affect threads created through app-server thread/start + turn/start, where the external orchestrator only receives acceptance, not final completion.
Environment
- Codex Desktop release observed in logs:
26.810.41047 - app-server version observed in logs:
0.148.0-alpha.9 - session
cli_version:0.146.0 - Host: local macOS
- Model provider:
codex_local_access - Timezone used below: Asia/Shanghai, UTC+8
Evidence
Example B, no compaction/handoff case:
- Thread:
01a00ee4-6014-7632-ae22-0aef4775476d - Turn:
01a00ee4-68c7-7492-9f8f-31fcd00750ae - Local JSONL:
$HOME/.codex/sessions/2026/08/17/rollout-2026-08-17T16-44-02-01a00ee4-6014-7632-ae22-0aef4775476d.jsonl - At
2026-08-17 16:52:07 UTC+8, the JSONL had 224 lines, latest event timestamp2026-08-17T08:52:07.812Z. - The JSONL contained no
final_answerand notask_complete. read_threadreturned the turn asstatus="interrupted",error=null,completedAt=null.wait_threads(timeoutMs=0/5000)woke withreason="turnCompleted", but the same response still hadlatestTurn.status="interrupted"andcompletedAt=null.- During polling, the JSONL kept growing, e.g. 206 -> 216 -> 224 lines, showing events were still being appended after the turn was treated as completed/interrupted.
- Desktop log around
2026-08-17T08:46:50.390Zshowedmaybe_resume_success ... latestTurnStatus=interrupted ... markedStreaming=true; the JSONL continued appending after that.
Example A, compaction/handoff-adjacent case:
- Thread:
01a00ec3-e645-7822-91db-1257005fd4c4 - A
phase="final_answer"handoff summary appeared mid-turn at2026-08-17 16:12:41 UTC+8. - The actual same-turn
task_completewas later at2026-08-17 16:17:19 UTC+8. - Desktop appeared to treat the turn as completed before the real
task_complete.
Expected Behavior
wait_threadsshould not wake withreason="turnCompleted"unless the turn has a real terminal state.read_threadand Desktop should not present a turn as completed/interrupted while the same turn is still receiving JSONL events.- A mid-turn
phase="final_answer"should not be treated as task completion withouttask_complete.
Actual Behavior
wait_threadscan wake asturnCompletedwhile the latest turn is stillinterruptedwithcompletedAt=null.- Desktop/read model can diverge from the underlying rollout JSONL stream.
- Users see background Codex tasks as if they stopped or completed halfway.
Impact
External orchestrators that create Codex tasks through app-server cannot reliably tell whether a turn genuinely finished. Desktop UI also makes background tasks look like they stopped or completed halfway.
Suspected Area
Codex Desktop/app-server read model or wait semantics for background-created turns. This is not limited to context compaction: compaction/handoff final_answer is one trigger shape, but the second example reproduces without final_answer or task_complete.
Diagnostics Snippets
Minimal excerpts only; full logs/JSONL may contain local paths, prompts, project names, message content, and tool outputs.
wait_threads contradiction
{
"wake": {
"reason": "turnCompleted",
"turnId": "01a00ee4-68c7-7492-9f8f-31fcd00750ae",
"threadId": "01a00ee4-6014-7632-ae22-0aef4775476d"
},
"latestTurn": {
"id": "01a00ee4-68c7-7492-9f8f-31fcd00750ae",
"status": "interrupted",
"error": null,
"completedAt": null,
"durationMs": null
}
}
JSONL terminal-event check
{
"line_count": 224,
"last_timestamp": "2026-08-17T08:52:07.812Z",
"finalLines": [],
"completeLines": []
}
Desktop resume log summary
At 2026-08-17T08:46:50.390Z, Desktop logged a successful resume for the same thread with:
maybe_resume_success ... latestTurnId=01a00ee4-68c7-7492-9f8f-31fcd00750ae latestTurnStatus=interrupted markedStreaming=true ... turnCount=1
The rollout JSONL continued appending after this log line.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
A second live reproduction on Codex Desktop 26.820.7780.0 (Windows ARM64, local CLI 0.149.1) shows a stronger form of the same background-turn projection failure when the target runs on a Linux Remote SSH host.
At one instant, the first-party task tools returned these conflicting views for the same target thread and turn:
read_thread: threadactive, latest turninProgress,completedAt=null.wait_threads(timeoutMs=0): threadactive, the same latest turninterrupted,completedAt=null.send_message_to_threadcalls succeeded and their inputs appeared in that existing turn, but no separate new turn was created.This caused the orchestrating task to conclude that the target thread was corrupted and needed replacement, although the target was still executing and receiving follow-ups. The failure therefore affects both lifecycle control and model decisions, not only the UI.
Minimal reproduction shape:
send_message_to_thread.read_threadandwait_threads(timeoutMs=0).inProgressin one projection andinterruptedin the other, while the aggregate thread remainsactive.Expected behavior:
wait_threadsmust not exposeinterruptedwhile later activity from that same turn is visible throughread_thread.I did not inspect or modify the remote JSONL or database for this reproduction. The evidence above comes only from live, read-only Codex task APIs. No task IDs, prompts, project names, hostnames, addresses, paths, or account data are included.
Fresh sanitized cross-API snapshot from Codex Desktop 26.820.7780.0 with CLI 0.149.1 and a task on a Linux Remote SSH host.
At the same instant, first-party task APIs returned mutually incompatible lifecycle views:
wait_threads(timeoutMs=0): aggregate threadactive, revision unchanged, latest turncompleted, final response present.read_thread: aggregate threadactive, but an older turn projected asinProgresswithcompletedAt=null.The orchestrating task interpreted the aggregate
activestate as evidence that new work had started, then entered repeated 60-second waits. In reality, the only authoritative latest turn exposed bywait_threadswas already terminal.This strengthens the lifecycle impact: the disagreement can cause an orchestrator to make incorrect control decisions and spend additional model turns waiting for work that was never started.
Expected behavior:
read_thread,wait_threads, and cross-thread send must share one authoritative latest-turn projection.activemust not coexist indefinitely with only a terminal latest turn unless the API exposes the distinct activity that keeps the thread active.No prompts, task IDs, project or repository names, paths, hostnames, addresses, account data, or credentials are included.
A fresh reproduction adds a recovery/control failure to the lifecycle projection problem.
Environment:
26.820.7780.0on Windows ARM64.codex-cli 0.149.1.Observed behavior:
activewith the same turn reportedinProgress,completedAt=null, for more than 24 hours. The latest visible tool marker was alreadycompleted, and no newer assistant progress was exposed.send_message_to_threadcall returned success and advanced the task'supdatedAt.1, the same old turn remained latest, and no new turn appeared.wait_threads(timeoutMs=0)andread_threadcontinued to expose the same stale commentary/tool boundary. Neither API indicated whether the accepted message was queued, steered into the ghost turn, or discarded.handoff_threadis not a safe substitute because it also moves execution placement and associated Git state.Impact:
Expected behavior:
send_message_to_threadreturn an explicit disposition: queued, steered into a live turn, started a new turn, or rejected.activetask whose latest turn has made no observable progress after its last completed tool.read_thread,wait_threads, aggregate status, and send disposition on one authoritative lifecycle state.This appears to be the same underlying projection defect as the issue, with an additional missing recovery primitive rather than a separate duplicate.
A fresh Remote SSH reproduction adds a four-way lifecycle disagreement plus a missing paginated-history projection for a structurally valid, actively growing thread.
Environment:
26.820.9563.0on Windows ARM64.0.149.1on Linux.019ee6ac-9514-7a21-b8fe-913884f7b98d.At the same time, first-party surfaces reported incompatible states for the same thread and current turn:
wait_threads: aggregate threadactive, current turninProgress, with fresh progress.thread/turns/list: the same current turn wasinterrupted.thread/read: thread statusnotLoadedand a staleupdatedAt.active.The fresh
thread/turns/listrequest returned in about 2.6 seconds, so this snapshot was not a generic transport timeout.Storage evidence:
User-visible impact:
no rollout founderror, then remained on “Loading task…”.Expected behavior:
thread/read,thread/turns/list, Desktop inventory, and native task APIs must use one authoritative latest-turn lifecycle.inProgress,interrupted,notLoaded, and aggregateactive.No repository name, project title, username, hostname, address, filesystem path, prompt content, or raw log is included.