[macOS App] Completed subagents stay open in thread_spawn_edges and restore stale MCP stacks
What version of the Codex App are you using (From “About Codex” dialog)?
26.707.91948 (build 5440), bundled codex-cli 0.144.5
What subscription do you have?
ChatGPT subscription. The exact tier is not exposed by the local diagnostics used for this report.
What platform is your computer?
Darwin 25.5.0 arm64 arm
What issue are you seeing?
Completed/interrupted subagents remain persisted as open rows in state_5.sqlite.thread_spawn_edges. When Codex Desktop restores the parent task, it starts one complete MCP/plugin helper generation for the root task and for every stale descendant edge—even though the children no longer appear in the live agent listing.
This creates a direct, measurable multiplier:
root task + 4 direct subagents + 1 nested subagent
= 6 restored contexts
= 6 MCP/plugin helper generations
Before the focused reproduction, a single app-server had reached:
generations=11
matched MCP/plugin direct children=99
matched child RSS=3798 MiB
app-server RSS=692 MiB
After a full application relaunch, the same task restored exactly six generations:
generations=6
matched MCP/plugin direct children=48
matched child RSS=3152 MiB
app-server RSS=632 MiB
The five descendant rows were still open, despite the agents being completed/interrupted and absent from the current live-agent listing:
parent child-1 open depth=1
parent child-2 open depth=1
parent child-3 open depth=1
parent child-4 open depth=1
child-3 nested-child open depth=2
There is a second lifecycle/config problem: restored contexts can start helpers from stale tool policy. xcodebuildmcp started four times even though the current plugin MCP override resolved it as disabled. A removed GitHits MCP definition did take effect and did not restart, so persisted tool policy appears inconsistent by source/layer.
What steps can reproduce the bug?
- In Codex Desktop, start one task with several subagents and at least one nested subagent. Use MCP-bearing plugins or configured stdio MCP servers.
- Let the children finish or interrupt them. Confirm they no longer appear as live agents.
- Inspect
thread_spawn_edgesfor the root task:
``sql``
WITH RECURSIVE tree(id, depth) AS (
SELECT '<root-thread-id>', 0
UNION ALL
SELECT e.child_thread_id, t.depth + 1
FROM thread_spawn_edges e
JOIN tree t ON e.parent_thread_id = t.id
)
SELECT e.parent_thread_id, e.child_thread_id, e.status, t.depth + 1 AS depth
FROM thread_spawn_edges e
JOIN tree t ON e.parent_thread_id = t.id;
- Fully quit and reopen Codex Desktop.
- Count repeated direct children under the new
codex app-serverPID (node_repl, pluginserver.mjs/server.cjs, Workshop, Pencil, XcodeBuildMCP, etc.). - Observe that the number of helper generations matches the root task plus the stale
opendescendant edges. - Disable a plugin-provided MCP in the current config, relaunch, and observe that restored contexts may still start that server from stale policy.
Controlled diagnostic
After an online SQLite backup, I changed only these five descendant edge rows from open to closed; PRAGMA integrity_check returned ok.
Restarting only app-server was insufficient: the still-running Electron main process retained cached task state and recreated five helper generations. This suggests there are two sources that must converge:
- persisted
thread_spawn_edgeslifecycle state; - cached restored-task/tool state owned by the long-lived desktop main process.
This database edit is diagnostic evidence, not a recommended user workaround.
What is the expected behavior?
- A completed, interrupted, or stopped subagent must transition its spawn edge to
closedexactly once. - Desktop restore must not treat completed descendants as live task contexts.
- Restored tasks must re-resolve the current MCP/plugin policy before launching helpers.
enabled = falsemust prevent launch from persisted or cached task state.- Finished descendants' MCP process groups must be reaped without requiring a full desktop restart.
- The UI/diagnostics should expose the owning task/subagent for every MCP helper and why it is still alive.
Additional information
Sanitized logs, state-table evidence, and active-config summary:
https://gist.github.com/ardjo-s/aec95bc245439bd447beb179b0bfb049
Related open issues:
- #32942 — restored tasks fan out plugin MCP servers and can ignore current Xcode MCP policy
- #19197 — persistent orphaned subagents and missing lifecycle controls
- #17574 — subagents retain stdio MCP helper trees
- #30408 — per-thread MCP processes are not cleaned up
- #11324 — MCP memory growth while multitasking in the App
- #21984 — eager per-session MCP startup
This report is intentionally focused on the persisted thread_spawn_edges.status = open invariant and the exact 1:1 mapping between stale descendant edges and restored MCP generations.
During diagnostics I intentionally sent SIGTERM to app-server to test teardown. The app displayed its generic crash page; that signal was operator-initiated and is not being reported as a spontaneous crash. The page also displayed an unrelated Semrush remote-plugin name-mismatch warning; it may deserve a separate report but is not presented here as the cause of the MCP/subagent lifecycle defect.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗