Skip to content

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

Open

Description

@Zanekingd

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

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.

Activity

  1. added
    bugSomething isn't working
    extensionIssues related to the VS Code extension
    subagentIssues involving subagents or multi-agent features
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on May 30, 2026
  2. grnbtqdbyx-create commented on May 31, 2026

    @grnbtqdbyx-create
  3. MisterRound commented on Jun 16, 2026

    @MisterRound

    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:

  4. MisterRound commented on Jun 22, 2026

    @MisterRound

    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.

  5. apple-ouyang commented on Jul 13, 2026

    @apple-ouyang

    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:

    • pub(crate) fn compute_source_filters(
      source_kinds: Option<Vec<ThreadSourceKind>>,
      ) -> (Vec<CoreSessionSource>, Option<Vec<ThreadSourceKind>>) {
      let Some(source_kinds) = source_kinds else {
      return (INTERACTIVE_SESSION_SOURCES.to_vec(), None);
      };
      if source_kinds.is_empty() {
      return (INTERACTIVE_SESSION_SOURCES.to_vec(), None);
      }
    • pub(crate) fn source_kind_matches(source: &CoreSessionSource, filter: &[ThreadSourceKind]) -> bool {
      filter.iter().any(|kind| match kind {
      ThreadSourceKind::Cli => matches!(source, CoreSessionSource::Cli),
      ThreadSourceKind::VsCode => matches!(source, CoreSessionSource::VSCode),
      ThreadSourceKind::Exec => matches!(source, CoreSessionSource::Exec),
      ThreadSourceKind::AppServer => matches!(source, CoreSessionSource::Mcp),
      ThreadSourceKind::SubAgent => matches!(source, CoreSessionSource::SubAgent(_)),
      ThreadSourceKind::SubAgentReview => {
    • let (allowed_sources_vec, source_kind_filter) =
      if relation_filter.is_some() && source_kinds.is_none() {
      (Vec::new(), None)
      } else {
      compute_source_filters(source_kinds)
      };

    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.

  6. refusedzero commented on Aug 7, 2026

    @refusedzero

    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.

  7. hol0slic3r commented on Aug 10, 2026

    @hol0slic3r

    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.

  8. hol0slic3r commented on Aug 10, 2026

    @hol0slic3r

    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.

  9. hol0slic3r commented on Aug 11, 2026

    @hol0slic3r

    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.

  10. boombx403-byte commented on Aug 18, 2026

    @boombx403-byte

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingextensionIssues related to the VS Code extensionsessionIssues involving session (thread) management, resuming, forking, naming, archivingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions