Subagent child threads are counted as top-level recent conversations and can leave stale open spawn edges

Open 💬 10 comments Opened May 30, 2026 by Zanekingd
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

Subagent child threads appear to be persisted as normal threads and returned by the ordinary recent conversation listing. When many subagents have been used, those child threads consume the global recent-conversation slots, so older real parent/user conversations look like they have disappeared from the sidebar even though they are still present locally.

There also appears to be stale lifecycle metadata: some rows in thread_spawn_edges can remain open long after the corresponding subagent work appears inactive.

Environment

  • Platform: Linux
  • Client: VS Code extension / desktop-style Codex UI
  • Local state DB inspected: ~/.codex/state_5.sqlite

Anonymized Local Evidence

From one local state DB:

  • Total threads: 553
  • Unarchived threads: 114
  • Subagent child threads linked by thread_spawn_edges: 61
  • Unarchived subagent child threads: 51
  • Unarchived non-child threads: 63
  • Parents with subagents: 14
  • thread_spawn_edges.status: 56 closed, 5 open
  • jobs, agent_jobs, and agent_job_items were empty
  • Process inspection did not show many active subagent workers, only the normal app-server process

No private thread titles, paths, or thread IDs are included here.

Queries Used

select status, count(*)
from thread_spawn_edges
group by status;

select c.archived, count(*)
from thread_spawn_edges e
join threads c on c.id = e.child_thread_id
group by c.archived;

with child as (
  select child_thread_id from thread_spawn_edges
)
select 'unarchived_threads', count(*) from threads where archived = 0
union all
select 'subagent_children_unarchived', count(*) from threads where archived = 0 and id in child
union all
select 'normal_unarchived_excluding_subagent_children', count(*) from threads where archived = 0 and id not in child;

Observed Behavior

  1. Use subagents over time so child threads are created.
  2. The child threads are stored in threads and linked from thread_spawn_edges.
  3. Many child threads remain archived = 0.
  4. The recent conversation list includes those child threads as ordinary top-level conversations.
  5. These child threads consume recent-list slots, making older parent/user conversations look missing from the sidebar.
  6. Some spawn edges remain open even when there is no obvious active agent job or worker process.

Expected Behavior

  • Subagent child threads should not be surfaced as top-level recent conversations by default, or should be grouped under the parent thread.
  • Subagent child threads should not consume the global recent-conversation page limit.
  • Completed/inactive subagent edges should reliably transition to closed, or stale open rows should be ignored/cleaned up.
  • Sidebar/search behavior should keep real parent/user conversations discoverable even when many subagents exist.

Why This Matters

In the inspected state, 51 unarchived child threads inflated the recent-list population while the actual unarchived non-child conversation count was only 63. This interacts badly with recent-conversation pagination/capping and makes real conversations appear to disappear.

Possibly Related Issues

  • #15368
  • #19197
  • #16817
  • #21128

This may be the connection between the recent-list cap and subagent lifecycle/state persistence: child subagent threads are counted as normal recent conversations, and some spawn edges remain stale-open.

View original on GitHub ↗

10 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #25179
  • #23931
  • #23930
  • #23966
  • #25084

Powered by Codex Action

grnbtqdbyx-create · 2 months ago

I added this child-thread/recent-list/spawn-edge drift case to trace-to-skill@0.1.66:

npx trace-to-skill@0.1.66 demo subagent-lifecycle
npx trace-to-skill@0.1.66 codex-report ./runs --output openai-codex-subagents.md

Not a Codex fix; it is a public fixture + report checklist for the exact state split this issue describes: child threads showing as top-level recent conversations, archived/unarchived child state, thread_spawn_edges open/closed counts, parent/root thread id, subagent ids/roles, list/close/readback behavior, quota/max_threads evidence, and whether stale children are only UI noise or still consume spawn slots.

Release: https://github.com/grnbtqdbyx-create/trace-to-skill/releases/tag/v0.1.66

MisterRound · 2 months ago

Additional current-build evidence from a Windows + WSL2 Desktop profile. This looks like the same bug class, but with a larger state split and a slightly different user-visible symptom: the subagent rows are not visible as normal sidebar chats, yet they remain hot in threads.archived = 0 and appear to affect thread/list.

Environment:

  • Windows 11 + WSL2 Ubuntu
  • Codex Desktop package: OpenAI.Codex_26.609.9530.0_x64__2p2nqsd0c76g0
  • Local state DB: state_5.sqlite

Sanitized state counts:

thread_source  archived  count
subagent       0         4425
<null>         0         78
subagent       1         35
user           0         18
<null>         1         16
user           1         15

Archived timestamp consistency:

archived  archived_at_present  count
0         false                4521
1         true                 66

Most unarchived subagent rows are from one WSL project / parent workflow:

unarchived subagent rows in largest WSL project: 4281
unarchived subagent rows in next WSL project: 50

Observed behavior:

  • The Desktop sidebar shows only a small filtered user/project thread set; these thousands of subagent rows are not shown as normal chats.
  • The user and agent expected these subagent threads to be archived/closed because they are absent from the UI.
  • The canonical DB state still has thread_source = 'subagent' AND archived = 0 for 4,425 rows.
  • After an app restart, thread/list is still very slow. In the latest two Desktop log files I found 97 thread/list calls with durationMs; all were over 2 seconds, with a max around 84 seconds and top values around 61s, 56s, 44s, 37s, 28s, 23s.
  • Ambient suggestion/list paths also timed out while this state existed.

Attempted local mitigation:

  • I made a DB backup and exported the target subagent IDs.
  • A live update intended to archive only thread_source = 'subagent' AND archived = 0 rows timed out while the Desktop app was running and the WAL was active.
  • Counts remained unchanged, so there is not currently a safe in-app cleanup/repair path for this profile.

Expected behavior:

  • Completed or UI-hidden subagent child rows should not remain in the hot unarchived top-level list population.
  • If subagent history must remain resumable, it should be grouped behind the parent or excluded from normal thread/list/sidebar metadata queries.
  • The UI archive/visibility model and the canonical threads.archived state should not diverge this way.
  • Existing profiles need a safe repair or compaction path; users should not need to manually edit state_5.sqlite.

Related issues I found while checking for duplicates:

  • #24510
  • #21211
  • #25179
MisterRound · 2 months ago

Follow-up: after quitting Desktop fully, archiving only thread_source='subagent' AND archived=0 updated 4,425 rows.

Later state showed:

subagent archived=1: 4453
subagent archived=0: 13

That confirms the hidden subagent rows were still in the hot unarchived thread population and could be moved out safely only while Desktop was stopped. The remaining product gap is a supported cleanup/repair path and preventing completed hidden subagents from accumulating as archived=0.

apple-ouyang · 1 month ago

Additional current-build evidence from Codex App on macOS. This is the same user-visible bug, with a more specific metadata mismatch that explains why some delegated child threads are returned as top-level conversations.

Environment

  • Codex App: 26.707.61608 (build 5200)
  • Bundled CLI: codex-cli 0.144.0-alpha.4
  • Platform: Darwin 25.5.0 arm64 arm
  • Active state DB: ~/.codex/state_5.sqlite
  • PRAGMA integrity_check: ok

Sanitized persisted shape

One Codex Desktop-created delegated child was persisted identically in SQLite and the rollout session_meta as:

source = vscode
thread_source = subagent
parent_thread_id = null
archived = 0
model_provider = <current provider>

Its first user item is a <codex_delegation> envelope containing a parent/source thread id, so it is a delegated child rather than an ordinary user-created conversation. However, the canonical parent relation is absent and its SessionSource is VSCode, not SubAgent::ThreadSpawn.

In the same DB:

  • 6 unarchived rows have the anomalous visible shape thread_source = subagent AND source IN (cli, vscode)
  • 1,618 native subagent rows use serialized SubAgent::ThreadSpawn / other subagent sources and do not have this shape
  • No manual DB edits or provider migration touched these rows

Why these rows enter the top-level list

Current upstream compute_source_filters(None) defaults to INTERACTIVE_SESSION_SOURCES, and source_kind_matches(VsCode) matches ThreadSourceKind::VsCode. The separate thread_source = subagent classification is not used to exclude the row:

The result is that these delegated children appear as normal top-level project conversations and consume recent-list slots.

Expected fix

Prefer persisting Desktop-created delegated children with the native SessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id, ... }) plus the canonical parent relation. As a defensive fallback, top-level sidebar queries could also exclude rows classified as thread_source = subagent when no parent/ancestor relation filter is requested.

This evidence was collected read-only; the DB and rollout files were not modified.

refusedzero · 21 days ago

Fresh Windows/Desktop reproduction (2026-08-06) that appears to confirm and amplify this issue:

Environment

  • Windows 11
  • Codex Desktop 26.730.8199.0
  • Bundled codex-cli 0.147.0-alpha.1.2
  • GPT-5.6 Sol
  • Multi-agent enabled

Observed state

  • thread_spawn_edges had 1,313 open and 1,689 closed rows.
  • One long-lived parent accounted for 269 direct open children.
  • Rollout terminal-event inspection showed 251 ended in task_complete and 18 in turn_aborted.
  • The app showed hundreds of agents as running even though only two top-level Codex tasks were actually active.
  • All rollout histories were still present; this was lifecycle/accounting drift, not missing session data.

After a backup-first reconciliation reduced the database to zero open edges, newly completed subagents immediately reproduced the problem: six child rollouts ended in task_complete but their spawn edges remained open.

Tool-surface A/B

I tested the documented stable feature separately:

  1. Set features.multi_agent = true.
  2. Set features.multi_agent_v2 = false.
  3. Fully restarted Codex Desktop.
  4. Verified the CLI reported stable multi-agent on and v2 off.

Result: close_agent was still absent from the Desktop task's callable collaboration-tool inventory, while other collaboration controls remained available. Newly completed children still remained open. The configuration was restored afterward.

This may connect the stale-edge behavior here with #33590, although in this reproduction collaboration tools were partially available rather than the entire v1 namespace being absent.

Requested safeguards

  • Automatically transition a spawn edge to closed when the child rollout reaches task_complete.
  • Reconcile completed-but-open edges during startup.
  • Expose a supported close_agent operation in the Desktop collaboration tool catalogue.
  • Derive Active/Done UI state from terminal rollout status when lifecycle metadata disagrees.
  • Avoid counting completed child threads as currently running agents.

Database integrity checks passed and no rollout histories were deleted during the local reconciliation.

hol0slic3r · 18 days ago

Fresh Windows/Desktop reproduction: completed parent disappears after 27-child run, with stale parent recency and ~9.9k unknown-child events

This adds an end-to-end user-visible reproduction to the child-thread/recent-list problem: a completed parent task disappeared from its project task view, while the full conversation remained recoverable by exact ID.

Environment

  • Windows 11 x64, build 26200
  • Codex Desktop 26.730.8199.0
  • Bundled codex-cli 0.147.0-alpha.1.2
  • GPT-5.6 Sol, Ultra reasoning, multi-agent enabled

Observed sequence

  1. One user-owned parent ran a single long implementation turn for about 6h 38m and spawned 27 direct child agents.
  2. Both parent turns ended normally with status=completed; the rollout ends in task_complete.
  3. The parent then disappeared from the project's normal task view.
  4. The parent was still archived=0, its rollout JSONL was complete, and an exact-ID read/open succeeded. Opening it directly made it visible again.
  5. The Codex process did not crash or restart; it remained the same responsive process throughout.

Timestamp drift
The durable indexes stopped aging the parent while its long turn continued:

session_index.jsonl updated_at: 2026-08-09T17:02:18Z
state_5.sqlite updated/recency: 2026-08-09T17:33:31Z
parent task_complete:             2026-08-10T00:11:59Z

The SQLite timestamp is effectively the long turn's start time, not its completion time. By completion, 32 rows had newer recency values: 26 subagent and 6 user rows. This makes the completed parent vulnerable to recent-list/project-view displacement even though it is the work the user just finished.

Renderer/conversation-state evidence
During the same parent turn, Desktop logged:

Received item/started for unknown conversation:   3,945
Received item/completed for unknown conversation: 3,935
Conversation state not found:                     2,001
Received turn/completed for unknown conversation:    49

Of these, 9,877 events mapped directly to all 27 child IDs. The parent conversation itself had zero error-severity log entries and completed normally.

At parent completion, Codex also failed to retain the optional turn-diff checkpoint:

requestKind=turn-diff-capture-complete
git update-ref ... exitCode=128
Failed to retain a turn diff tree snapshot

The repository working state and complete parent history remained intact, so this checkpoint failure was not data loss, but it occurred at the same completion boundary where parent metadata should have been refreshed.

Assessment
This looks like the combination described in this issue:

  • child rows/events remain hot enough to dominate recent-task state;
  • the renderer receives child events without registered conversation state;
  • the parent updated_at / recency_at is not refreshed during or after a long multi-agent turn;
  • the user-visible parent can therefore disappear even though it completed successfully and remains locally recoverable.

This also provides durable-state evidence for the UI symptom in #34125 and overlaps the unknown-child event path in #32738.

Expected safeguards

  • Refresh the parent task's recency on activity and terminal completion.
  • Do not let child rows consume top-level project/recent-task slots.
  • Register/hydrate child conversation state before broadcasting child events, or intentionally filter those events from the parent renderer.
  • Reconcile sidebar/project visibility from terminal rollouts when index timestamps drift.
  • Keep turn-diff checkpoint failures isolated from task visibility.
  • Provide a supported reindex/recovery action so users do not need exact thread IDs.

Raw logs, task IDs, titles, prompts, and local paths are omitted for privacy. I can provide narrowly scoped redacted excerpts if maintainers request them.

hol0slic3r · 18 days ago

Follow-up on the same reproduction: the recovered parent disappeared again immediately after a direct-ID navigation/open.

This produced a useful recovery A/B:

  1. Exact-ID read succeeded.
  2. Direct navigation opened the task, but that visibility was temporary; the task vanished again.
  3. The local thread row remained archived=0 and still reported is_pinned=0.
  4. I then used the app's explicit persistent pin operation for that exact thread.
  5. After a fresh navigation plus task-list refresh, the parent remained present as pinned index 1 and idle.

So merely opening a recoverable hidden parent does not refresh/persist its project-list state or stale recency. Explicit pinning is currently a viable workaround, but users should not have to know the hidden thread ID or pin every affected parent. This strengthens the case for refreshing parent recency/visibility on direct open and terminal completion.

hol0slic3r · 17 days ago

Windows recurrence: five completed user parents omitted; direct open did not restore them; pinning was required

This is a broader recurrence of the behavior reported in comments 5236953035 and 5236967525.

Environment

  • Windows 11 x64, build 26200
  • Incident tasks ran under Codex Desktop 26.730.8199.0
  • Bundled codex-cli 0.147.0-alpha.1.2
  • Recovery was re-verified after intervening upgrades/restarts on Desktop 26.803.10989.0
  • PRAGMA integrity_check: ok

Recovery A/B

  1. A durable overnight inventory found ten user-owned root tasks with intact rollout histories.
  2. Five completed roots were absent from a fresh top-level list_threads(limit=50); all five were archived=0 and thread_source=user.
  3. Exact-ID reads succeeded and showed completed turns for all five.
  4. Exact-ID navigation returned success for each root, but a fresh list still omitted all five.
  5. Explicitly pinning the five roots made them persist immediately at pinned positions 1–5.
  6. No SQLite, rollout, repository, or transcript content was edited.

The intervening Desktop upgrade/restarts did not self-heal list membership. This reproduces the earlier direct-open-versus-pin distinction across five roots and three unrelated projects.

Parent recency and child-lifecycle drift

For all five missing roots, session_index.jsonl.updated_at stopped before the terminal completion event—by approximately 2m33s, 43m, 1h17m, 2h45m, and 2h50m. SQLite recency_at_ms was likewise near initial activity rather than terminal completion.

During the core overnight creation window, 77 new thread rows were added:

thread_source=subagent: 69
thread_source=user:      8

Current profile state remains hot with completed children:

unarchived subagent rows: 229
unarchived user rows:      80
thread_spawn_edges open:  131
thread_spawn_edges closed: 43

Four of the five missing roots owned 26 direct child edges. Every one of those 26 child rollouts ends in task_complete, yet all 26 edges remain open. The fifth missing root had no children, which suggests global child/list contamination can displace an unrelated root too.

Log correlation

Across 63,928 Desktop log lines in the overnight window:

Received ... for unknown conversation: 31,619
Conversation state not found:           10,355
IAB_LIFECYCLE records:                   1,352
IAB backend request failed:                196
Failed turn-diff captures:                    8

There were zero instances of the previously observed GPU-crash, browser-renderer-gone, code-mode-host-exited, or stdio-transport-closed signatures. Windows Application/WER also had no Codex fault for this window. The AppX update/restart occurred only after the overnight tasks had completed.

So this incident is not transcript loss and does not appear to be another native crash. It is a persistent discoverability/indexing failure with stale parent recency, completed-but-open child edges, and large volumes of unregistered child conversation events. Pinning is currently an effective recovery workaround when the hidden exact IDs can be found.

Requested safeguards

  • Transition child spawn edges to closed when their rollouts reach task_complete, and reconcile completed-but-open edges at startup.
  • Exclude or group child rows so they do not consume top-level project/recent-task slots.
  • Refresh parent updated_at / recency_at at terminal completion and after successful direct navigation.
  • Rebuild sidebar/project visibility from durable rollout state when indexes drift.
  • Provide a supported “recover/reindex missing tasks” action so users do not need exact hidden IDs and manual pinning.
  • Ensure pin/list visibility survives Desktop upgrades and state migrations.

Task IDs, titles, prompts, project names, paths, usernames, and raw logs are omitted for privacy.

boombx403-byte · 9 days ago

Hi @Zanekingd, your report looks related to a local session discovery/inventory gap rather than necessarily a lost rollout. I've released Codex Rescue Alpha5, a local read-only diagnostic tool that performs filesystem-first discovery across active and archived rollouts and cross-references SQLite/index metadata without modifying any state.

If you still have the affected local Codex directory, you can inspect it via:

pip install codex-rescue==0.1.0a5
codex-rescue sessions --json

No raw files or databases are needed. If you choose to share output, please review and sanitize any private repository paths or names first.