Codex Desktop: completed subagents restore as Active after reload
Version and platform
- Codex Desktop for Windows x64
- Store package
OpenAI.Codex 26.730.8199.0 - Installed
app.asarSHA-256:acba5f408b7c6c909ffbfdf3c7d3f10660897bfbbb10f916a78505986b44b772
Bug
After Desktop reloads a task with a long subagent history, completed child agents are restored under Active instead of Done.
This is not a small count drift. In one reproducible parent task:
| Evidence | Count |
|---|---:|
| Direct child spawn edges | 126 |
| Child rollout ends in task_complete | 117 |
| Child rollout ends in turn_aborted | 6 |
| Nonterminal or unresolved | 3 |
| Desktop panel shows Active | 109 |
| Desktop panel shows Done | 17 |
At least 100 durably completed children are therefore displayed as active.
The underlying child records are not lost. A direct thread/read for an affected child returns thread.status = notLoaded and a latest turn whose status is completed.
Reproduction
- Open a Desktop task.
- Spawn and complete enough subagents for the parent to retain several historical children.
- Restart Desktop or otherwise cold-hydrate that task.
- Open the Subagents panel.
- Observe completed children under Active.
- Directly read one of those children: the runtime thread status is
notLoaded, while its latest durable turn iscompleted.
Root cause
The shipped renderer's descendant discovery uses:
thread/list
ancestorThreadId=<parent>
sourceKinds=["subAgentThreadSpawn"]
useStateDbOnly=true
Stored children that are not loaded in the current app-server process correctly arrive with runtime ThreadStatus.notLoaded. The renderer then combines that with the parent's historical subAgentActivity state. Parent activity has started, interacted, and interrupted, but no durable completed activity kind. As a result, the stale parent-side running reference wins and the child is projected as Active.
The renderer already has a correct latest-turn fallback for descendants that were observed but missing from thread/list: it reads turn history and maps terminal latest turns away from Active. That fallback is not applied to descendants that were successfully returned by the state-DB list, which is exactly the cold-reload case above.
thread_spawn_edges.status = open is not a runtime status and should not be changed or interpreted as Active/Done.
Minimal safe fix
During descendant discovery, hydrate only listed children whose runtime status is notLoaded:
thread/turns/list
threadId=<child>
cursor=null
limit=1
sortDirection=desc
itemsView=notLoaded
Projection:
- latest
completed-> Done - latest
failedorinterrupted-> existing failure/interruption policy; never Active - latest
inProgressreturned by the current app-server -> Active; the server normalizes stale non-runninginProgressturns tointerrupted, so this represents the list-to-live race - no turn/read failure -> remain unknown and stay out of Active; do not guess Done
- already loaded runtime
active,idle, orsystemErrorrecords are not queried or overwritten - live
thread/status/changed=activeevidence wins over the hydrated historical snapshot
To keep the large-history path bounded:
- use one class-wide semaphore to cap concurrent latest-turn reads (a local patch uses 8),
- deduplicate only simultaneous in-flight reads for the same
(threadId, updatedAt), - remove settled results so second-resolution
updatedAtcannot make same-second lifecycle changes stale and the cache cannot grow without bound, - return only one latest turn over the wire,
- do not repurpose spawn-edge status.
Performance caveat: in the current legacy app-server path, thread/turns/list limit=1 still reconstructs the child's full rollout internally before truncating the response. A renderer-only correction therefore performs up to one full replay per cold notLoaded child and can repeat that work on later discovery cycles. The production fix should expose an indexed latest lifecycle projection in descendant listing or use indexed turn metadata; the renderer candidate must not be presented as performance-proven on a 126-child live history until that cost is measured.
Regression coverage requested
- Cold restart: parent has only
started/interacted; child runtime isnotLoaded; latest turn iscompleted-> Done 1, Active 0. - A list-to-live race returns latest
inProgressfrom the current app-server -> Active 1, Done 0. - Latest turn is
interruptedorfailed-> not Active; keep the terminal turn available to the existing UI policy. - Missing/corrupt history -> unknown, not guessed Done.
- A same-second empty/interrupted read followed by completed is reread and projected Done.
- Simultaneous reads for the same generation are deduplicated, but a later discovery rereads it.
- Loaded runtime
active,idle, andsystemErrorare never queried or overwritten. - Two simultaneous large discovery calls share a global concurrency cap of 8.
- Mixed 126-child fixture -> Done 117, Active 2, interrupted excluded 6, unknown excluded 1.
- Real legacy 126-child hydration -> measure wall time, peak memory, and renderer responsiveness, or use an indexed backend projection that avoids full rollout replay.
A byte-guarded local renderer patch passes a regression that executes the exact injected production hydration methods, the shipped HFs projection, and the shipped side-panel Done/Active predicates. The 126-child fixture produces Done 117 and Active 2; six interrupted and one unknown child stay out of Active. The signed Store package was not modified.
15 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Confirmed on macOS. After restarting or reloading a task, 223 completed historical subagents remain displayed as Active despite no live workers. The affected child threads return
notLoaded, while their latest durable turns are completed, matching the diagnosis in this issue.I’m experiencing the same issue on Codex for Windows 26.803.5235.0.
Historical subagents can be moved from Working to Done by opening each one, but after fully quitting and relaunching Codex, the panel reconstructs the same incorrect state: 14 Working and 2 Done. The 14 displayed runtimes then continue increasing.
This persists after setting:
[agents]
enabled = false
The setting is loaded successfully and no multi-agent tools are available, so this appears to be a persisted-status/UI reconciliation problem rather than active agent execution.
Feedback ID: 019fd8ce-77e5-7cd0-8303-bb92cc9ed4ed
Confirmed on macOS 26.6.1 arm64 with Codex Desktop 26.803.41515 (build 6321).
A long-running task displayed 112 historical subagents under Active / Working, including entries with elapsed times exceeding three days. An authoritative agent-tree check showed only the parent task running; the recent child audits were completed. Attempting to interrupt one displayed historical child returned
not_found.A read-only process check also found the normal Codex application processes—not 112 active workers—and account utilization remained stable. This supports the issue’s diagnosis that completed historical children are being reconstructed as Active rather than actually executing.
No project names, repository paths, prompts, thread IDs, account information, or screenshots are included because the local labels contain private project context.
Independent reproduction on Codex Desktop for Windows, observed 2026-08-13.
In a long-running parent task with many historical delegation waves, the Subagents panel showed:
At the same time, an authoritative runtime status query returned the parent as the only running agent and every listed child as
completed. The completed children had already returned final results; no child work remained active.This makes the Working counter entirely non-authoritative for the affected task and strongly matches the stale/cold-hydrated lifecycle projection described here. The practical impact is that users cannot tell whether background work or concurrency slots are genuinely active.
A cropped screenshot showing only the Subagents counters was captured; it contains no project or conversation data.
Additional reproduction on Arch Linux, observed 2026-08-13.
This is the unofficial AUR repackaging of the Codex Desktop bundle, so I am including the exact versions:
openai-codex-desktop 26.803.81509-8codex-cli 0.147.0-alpha.6.6In a long-running parent task, the Subagents panel showed 209 Active / Working and 41 Done after a full host reboot.
A sampled child still displayed as Working had authoritative state
thread.status = notLoadedand latestturn.status = completed; its turn had completed roughly 11 hours earlier. The machine had rebooted after that completion, so it could not be a surviving worker process. A read-only process-tree check showed the normal Desktop app-server/renderer processes, not a corresponding population of live workers.The parent had 231 direct persisted spawn edges whose edge status was
open. I am reporting that only as persistence-shape evidence; consistent with this issue, I did not interpret or modify spawn-edge status as runtime state.Restart/reboot did not reconcile the panel. This appears to confirm the same cold-hydration/status-projection mismatch on Linux and on the newer bundled core.
No task names, repository paths, prompts, thread IDs, account data, or screenshots are included.
Just asking but, isn't this issue a duplicate of the following one?
#37041
Additional Windows reproduction, observed 2026-08-16, confirms that this issue still persists in the newer Desktop build.
OpenAI.Codex 26.810.7004.0idle; its latest parent turn iscompleted.started, 182interacted, 10interrupted, and nocompletedactivity kind, matching the stale-projection signature described in this issue.thread.status = notLoadedwith latestturn.status = completed.This confirms the cold-hydration bug is still reproducible on Windows after the originally reported
26.730build and remains present in26.810.7004.0.No project names, repository paths, prompts, thread IDs, account data, local state files, or screenshots are included. No cache, database, task, or project data was modified during diagnosis.
This is a strong persisted-lifecycle control case for
codex-rescue. The interesting part is that the child rollouts are durable and terminal, while the UI projects them as Active after cold reload. I’m field-testing whether Rescue can inspect parent/child session evidence and classify completed vs. genuinely unfinished work conservatively without trusting stale UI state.If you still have an affected parent task locally, could you run:
A healthy/no-damage result is useful here too; the tool should not infer corruption merely from the Active counter. If it does report an unfinished state, I’d want the sanitized finding and exit code before any salvage attempt.
Please don’t post raw rollouts/SQLite, prompts, child IDs, credentials, or private paths. Repo: https://github.com/shleder/codex-rescue
Reproduced on macOS with Codex Desktop 26.810.41047.
After fully quitting and relaunching Codex, the Subagents panel shows three historical child agents as Running.
Observed behavior:
Read-only local evidence:
sub_agent_activityentries withkind=startedand onekind=interacted, but no completed activity kind.task_complete.list_agentsquery reports all three children ascompleted.FINAL_ANSWERinter-agent messages, so the parent correctly received their results.This appears to match the cold-hydration/status-projection issue described here: completed child turns are durable, but the Desktop sidebar reconstructs them as Running after restart.
Raw rollout files are omitted because they contain private project paths, prompts, and task content.
I can reproduce the same user-visible bug on a newer Windows build, and in this case it affects both local tasks and tasks running through Remote SSH.
User-visible problem
Several parent tasks in the main Codex sidebar show badges indicating that multiple subagents are still processing.
However:
This makes the sidebar look as though multiple background agents are still consuming resources, even though no such child execution exists.
Environment
26.818.3698.0(x64)0.148.0Scope of the affected tasks
The stale processing state was found on three separate parent tasks:
Their recorded child relationships contained 48 child threads in total:
Every one of those 48 child threads was queried individually from its actual host.
Results:
| Child location | Count | Runtime thread status | Latest turn status |
|---|---:|---|---|
| Local Windows | 6 | 6
notLoaded| 5completed, 1interrupted|| Remote SSH | 42 | 42
notLoaded| 41completed, 1interrupted|| Total | 48 | 48
notLoaded| 46completed, 2interrupted|There were zero children with an active runtime status.
One parent task itself was still active, but it had no active child agents. The sidebar was therefore conflating parent activity or persisted child history with live subagent execution.
Persisted-state evidence
The local and remote Codex state databases were inspected separately.
Remote state database:
thread_spawn_edges;status='open';notLoaded;Local state database:
status='open';notLoadedchildren;Therefore, the persisted spawn-edge state disagreed with both the latest child-turn state and the live-agent registry.
Why restarting does not fix it
The stale
openrows survive application shutdown.After Codex starts again, the sidebar restores the parent/child relationships from persistent state. The children are either terminal or not loaded, but they are still projected as processing instead of being reconciled against their latest turn status.
The observed state flow is:
An
openspawn edge may be intended to preserve resumability or history. The problem is thatopenornotLoadedis being presented as active execution without checking the child’s latest turn.Expected behavior
completedorinterruptedlatest turn must never be counted as actively processing.notLoadedmust not default to Active.This provides current-version Windows plus Remote SSH evidence for the same state-reconciliation mechanism described in this issue, and also appears related to #37916, #37876, and #33700.
I've seen this problem since ChatGPT/Codex app merge inception on Windows.
not just windows, seen also on macosx and on last linux gui app too, the source is sort of deeper than the platform UI
Cross-case Windows evidence: after child rollouts reached task_complete and no child remained in the live registry, reopening the parent projected the same children as Active/Working again. Historical parent activity plus notLoaded child state matches the projection gap here and connects the UI symptom to #38364, #37916, and the capacity failure in #39694.
One cross-check for people gathering evidence here: the panel renders projection state, while rollouts are canonical.
vetto rescue --json scanreads only the JSONL layer, so diffing its session list against what the Subagents panel restores is a quick way to demonstrate the mismatch is derived-state-only.