Repository navigation
Codex Desktop: background turn reported completed/interrupted while JSONL continues without final_answer or task_complete #38972
Description
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.
Activity
github-actions commentedon Aug 17, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
stream disconnected before completionduring long-running tool operations while work continues #37647Powered by Codex Action
nos1609 commentedon Aug 26, 2026
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.
nos1609 commentedon Aug 26, 2026
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.
nos1609 commentedon Aug 26, 2026
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.
nos1609 commentedon Aug 27, 2026
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.
Document ranked GitHub issues for lifecycle disagreement
randzzD89 commentedon Sep 4, 2026
Fresh sanitized reproduction on macOS with
codex-cli 0.152.0confirms the same false-terminal read/wait projection on a newer build.Observed sequence for one background app-server turn:
read_thread/ wait subsequently projected the turn asstatus="interrupted"withcompletedAt=null.turn/interrupt,Op::Interrupt, cancel, abort, provider limit/error, reset event, or persistedturn_abortedwas found.interruptedobservation.task_completefor that turn.This caused the external Supervisor/orchestrator to stop the work early because it treated the read/wait projection as an authoritative terminal state, even though the owned rollout was still progressing and eventually completed normally.
This appears to be a direct match for the issue's original failure mode rather than a separate cancellation path.
Suggested invariant for app-server consumers: a reconstructed/read-model
interruptedwithcompletedAt=nullshould not be considered authoritative terminal evidence while the same owned turn is still producing persisted activity and no true terminal/abort event exists.No prompts, task/thread IDs, repository/project names, local paths, account data, credentials, or user content are included here. Full local forensic evidence was preserved privately if maintainers need additional sanitized fields.