Persistent orphaned subagents, missing lifecycle controls, and eventual session freezes

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

What version of Codex CLI is running?

0.123.0

What subscription do you have?

Pro+ ($200)

Which model were you using?

gpt-5.4 high

What platform is your computer?

Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-90-generic x86_64)

What terminal emulator and version are you using (if applicable)?

tmux

What issue are you seeing?

Summary

Codex leaks subagents and does not manage their lifecycle reliably. After enough subagents accumulate, Codex itself hits the too many subagents launched limit and attempts cleanup, but usually terminates only 1–3 subagents. The remaining stuck/orphaned subagents are no longer visible to it, continue to count against the limit, and block further launches. If I ask the main thread to clean up inactive subagents, the result is the same: only partial cleanup, with the remaining subagents still stuck and still counted. Over time, the session can hard-freeze mid-task and must be killed manually.

This does not look like simple memory exhaustion: free RAM is still available and swap is large when the freeze happens.

This is not an MCP/apps issue.
I had already disabled apps explicitly in codex-config.toml because MCP/apps initialization was previously getting stuck indefinitely:

[features]
multi_agent = true
apps = false

Actual behavior

  • Subagents can remain stuck after their work is finished.
  • Codex eventually reaches its own subagent limit.
  • When this happens, Codex may try to clean up automatically, but usually terminates only 1–3 subagents.
  • The remaining stuck/orphaned subagents are not properly visible to Codex anymore.
  • Those invisible subagents still count against the launch limit.
  • Asking the main thread to clean up inactive subagents produces the same partial cleanup.
  • Entering a subagent through /agents does not provide a way to terminate it.
  • There are no usable user-facing lifecycle controls for subagents.
  • In long-running degraded sessions, Codex can hard-freeze and must be killed manually.
  • This can happen even with plenty of free RAM and large swap available.

<img width="510" height="243" alt="Image" src="https://github.com/user-attachments/assets/b6e9e8ce-8c0d-475d-90c5-9ec1c11d054c" />

What steps can reproduce the bug?

  1. Use a session heavily with subagents.
  2. Let Codex spawn multiple subagents across tasks.
  3. After some time, some subagents remain alive after their useful work is already finished.
  4. Codex reaches the subagent limit and starts failing with too many subagents launched.
  5. Codex attempts cleanup on its own, but usually removes only 1–3 subagents.
  6. The remaining stuck/orphaned subagents are not cleaned up and appear to be no longer visible to Codex.
  7. Asking the main thread to clean up inactive subagents leads to the same partial result.
  8. New subagents still cannot be launched reliably.
  9. If the session keeps running in this degraded state, Codex may eventually freeze and require a manual kill.

What is the expected behavior?

  • Codex should track all active subagents accurately.
  • Finished, inactive, or orphaned subagents should remain visible until fully cleaned up.
  • Automatic cleanup should remove all inactive/orphaned subagents, not just 1–3.
  • Manual cleanup from the main thread should also remove all inactive/orphaned subagents reliably.
  • Stuck subagents should not continue counting against the limit after cleanup is requested.
  • /agents should allow explicit termination of the current subagent.
  • Users should have direct lifecycle controls for listing, terminating, force-killing, and resyncing subagents.
  • Leaked subagents should not be able to degrade the session into a blocked or frozen state.

Impact

This is a serious blocker for subagent-heavy workflows. Instead of enabling parallel decomposition, the current behavior causes degraded sessions, blocked launches, repeated manual recovery attempts, and eventual session loss.

Related issue

There is also a separate delegation quality problem: even when the main session runs on GPT-5.4, Codex often delegates unsuitable tasks to Codex 5.2, including research or spec writing, and often adds unnecessary restrictive instructions. This is separate, but it makes the subagent failure mode worse.

View original on GitHub ↗

12 Comments

github-actions[bot] contributor · 2 months ago

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

  • #18335
  • #19092
  • #17574

Powered by Codex Action

vzd3v · 2 months ago
Potential duplicates detected. Please review them and close your issue if it is a duplicate. Bug: Agent spawn slots leak across turns in persistent sessions (app-server / interactive CLI) #18335 MCP servers started for Codex subagents remain running after the subagent completes and close_agent succeeds #19092 * Subagents leak stdio MCP helper trees in Codex App; xcodebuildmcp and chrome-devtools-mcp accumulate indefinitely #17574 _Powered by Codex Action_

I reviewed the suggested duplicates.

This is not an MCP/apps issue on my side.

I had already disabled apps explicitly in codex-config.toml because MCP/apps initialization was previously getting stuck indefinitely:

[features]
multi_agent = true
apps = false
pavbar · 2 months ago

Adding a narrower abort-path reproduction here instead of opening another issue, because this appears to fit the same orphaned-subagent lifecycle family. I searched existing issues first for close_agent, wait_agent, subagent, and turn_aborted.

My read of the related issues: #18335 is the closest earlier technical report but is closed and framed around spawn-slot leaks in persistent sessions; #17574/#19092 are mostly MCP helper process leaks; #19344 is broader app freeze/session loss; and #20908 is recovery messaging. This issue looks like the best active place for evidence about a subagent that has a turn_aborted terminal event but still cannot be waited/closed correctly.

What version of the Codex App are you using?

I did not capture the About Codex dialog version during the repro. The affected session metadata reports:

cli_version: 0.130.0-alpha.5
originator: Codex Desktop

What subscription do you have?

ChatGPT Pro.

What platform is your computer?

Darwin 25.4.0 arm64 arm

What issue are you seeing?

A Codex Desktop subagent that was interrupted/aborted became a stale lifecycle handle:

  • wait_agent returned completed statuses for other subagents, but no terminal status for the interrupted one.
  • close_agent against the interrupted/orphaned subagent blocked indefinitely until I manually interrupted the parent turn.
  • The stale subagent still appeared in local session state, but no longer behaved like a closeable/waitable agent.

This is a narrower form of the orphaned-subagent behavior described in this issue: the trigger was an explicit interrupted/aborted subagent turn.

What steps can reproduce the bug?

  1. In Codex Desktop, spawn several explorer subagents from one parent thread.
  2. Let several subagents complete normally.
  3. Interrupt/abort one subagent turn before it reaches a normal final result.
  4. From the parent thread, call wait_agent for all subagent ids.
  5. Observe that completed agents return terminal statuses, but the interrupted agent returns no status.
  6. Call close_agent for the interrupted agent id.
  7. Observe that close_agent hangs until the parent turn is manually interrupted.

Affected identifiers from my repro:

parent_thread_id: available on request
subagent_id: ...fd638
subagent_turn_id: ...dd72
thread_name: <redacted private task name>

Local session index entry for the affected subagent:

{"id":"...fd638","thread_name":"<redacted private task name>","updated_at":"<redacted timestamp>"}

Session file tail for that subagent:

{"type":"event_msg","payload":{"type":"task_started","turn_id":"...dd72"}}
{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"<turn_aborted>..."}]}}
{"type":"event_msg","payload":{"type":"turn_aborted","turn_id":"...dd72","reason":"interrupted"}}

What is the expected behavior?

  • wait_agent should surface a terminal state for interrupted/aborted subagents, for example aborted, cancelled, or closed.
  • close_agent should be idempotent and return quickly for already-aborted agents.
  • If the backend cannot resolve the subagent state, close_agent should return a structured timeout/error instead of blocking the parent turn indefinitely.

Additional information

Observed in a normal Codex Desktop subagent workflow; sharing reproduction details and local state evidence per the repo contributing guidance.

sockpuppet9000 · 2 months ago

Adding one more concrete abort-path data point because it appears to match the same orphaned-subagent lifecycle failure.

What version of Codex CLI is running?

cli_version: 0.130.0-alpha.5
originator: Codex Desktop

I did not capture the About Codex app dialog version during the repro.

What subscription do you have?

ChatGPT Pro.

What platform is your computer?

macOS 26.3.1 (a), build 25D771280a
Darwin 25.3.0 arm64

What issue are you seeing?

A spawned subagent entered an interrupted/aborted state and then behaved like a stale runtime handle:

  • The subagent rollout contains only startup plus an aborted turn marker, not a normal assistant result.
  • close_agent submitted shutdown and marked the persisted spawn edge as closed.
  • The runtime did not finish teardown cleanly.
  • Hours later, the app-server still treated the same subagent thread as a running thread with active_turn_status=Interrupted.
  • A later cleanup attempt that included close_agent for this subagent did not return before the parent turn was manually interrupted.

Identifiers, partially redacted:

parent_thread_id: 019e18ff...7454
subagent_id:     019e2054...e457
subagent_turn_id:019e2054...a5b

Subagent rollout tail:

session_meta
event_msg/task_started
response_item/message <turn_aborted> The user interrupted the previous turn on purpose...
event_msg/turn_aborted

Relevant logs for the subagent:

2026-05-13 08:05:01Z DEBUG codex_core::session::handlers
  Submission { op: Shutdown }

2026-05-13 08:05:01Z TRACE codex_core::tasks
  aborting running task task_kind=Regular sub_id="019e2054...a5b"

2026-05-13 08:05:01Z WARN codex_core::tasks
  task 019e2054...a5b didn't complete gracefully after 100ms

2026-05-13 11:09:31Z DEBUG codex_app_server::request_processors::thread_lifecycle
  composing running thread resume response ... active_turn_present=true
  active_turn_id=Some("019e2054...a5b")
  active_turn_status=Some(Interrupted)

Persisted spawn edge state after the failed/partial cleanup:

child_thread_id: 019e2054...e457
status: closed

So the local DB state and the runtime state appear inconsistent: the edge is already closed, but the app-server still sees an interrupted active turn for that thread.

What steps can reproduce the bug?

  1. In Codex Desktop, spawn one or more subagents from a parent thread.
  2. Interrupt/abort one subagent turn before it reaches a normal final assistant result.
  3. From the parent thread, call close_agent for the interrupted subagent.
  4. Observe shutdown beginning, but the child task does not complete gracefully.
  5. Later, inspect app-server/session state or try cleanup again.
  6. Observe the subagent can still appear as a running/interrupted thread even though the spawn edge is persisted as closed, and close_agent can block instead of returning quickly.

What is the expected behavior?

  • Interrupted/aborted subagents should have a model-visible terminal lifecycle state.
  • close_agent should be idempotent and should return quickly for already-interrupted or already-persisted-closed subagents.
  • If runtime teardown cannot complete, close_agent should return a structured timeout/error instead of blocking the parent turn indefinitely.
  • Persisted spawn-edge state and app-server in-memory/runtime state should not diverge in a way that leaves an uncloseable stale handle.
TacoRocket · 1 month ago

Adding a current-version data point from Codex Desktop, but this appears to be the shared Codex core / app-server multi-agent lifecycle path rather than a GUI-only problem.

Version / surface

codex-cli 0.132.0
Codex Desktop app
macOS / Darwin arm64

The installed Desktop app includes the bundled codex CLI binary and exposes the same core/app-server protocol. The relevant symbols and source paths are in codex-rs/core/src/agent/* and codex-rs/core/src/tools/handlers/multi_agents*, not only in the GUI layer.

What I observed

In a long-running local project session that uses subagents heavily, completed subagents continued to count against the subagent pool until the parent explicitly called close_agent.

wait_agent observed completion, but did not release the slot. After enough completed-but-not-closed workers accumulated, new spawn_agent calls hit the pool limit even though the workers had already returned their final handbacks.

This matters for real project workflows where subagents are used to isolate risky implementation/review slices. Some examples that do not require project-specific details:

  • DB/storage slices where a worker may have opened local test processes or held a write lock during validation.
  • API/MCP integration tasks where worker cleanup needs to preserve evidence/history but should not strand a slot forever.
  • Long-running supervisor/service work where the parent can compact or run out of tool budget before issuing all expected close calls.

I agree with earlier maintainer comments that a completed agent may still have background terminal processes or MCP connections, so blindly deleting thread state is not safe. The issue is that the only safe release path appears to be explicit close_agent, and the parent model can miss that call.

Code-level read

From rust-v0.132.0:

  • AgentControl::spawn_agent_internal reserves a pool slot with self.state.reserve_spawn_slot(config.agent_max_threads).
  • AgentRegistry::reserve_spawn_slot checks/increments total_count and returns AgentLimitReached when the limit is hit.
  • AgentRegistry::release_spawned_thread is the release path that removes the spawned agent from the active tree and decrements total_count.
  • release_spawned_thread is called from AgentControl::shutdown_live_agent, which is reached by AgentControl::close_agent, and from the InternalAgentDied cleanup path.
  • Normal completion maps TurnComplete to AgentStatus::Completed(...).
  • is_final treats Completed, Errored, Shutdown, and NotFound as final, but normal final status does not call release_spawned_thread.
  • wait_agent observes final status and emits waiting events; it does not close or release the agent.

Relevant paths/functions:

codex-rs/core/src/agent/registry.rs
  AgentRegistry::reserve_spawn_slot
  AgentRegistry::release_spawned_thread

codex-rs/core/src/agent/control.rs
  AgentControl::spawn_agent_internal
  AgentControl::shutdown_live_agent
  AgentControl::close_agent
  AgentControl::maybe_start_completion_watcher

codex-rs/core/src/agent/status.rs
  agent_status_from_event
  is_final

codex-rs/core/src/tools/handlers/multi_agents*/wait.rs
codex-rs/core/src/tools/handlers/multi_agents*/close_agent.rs

Thread APIs are not an equivalent workaround

I also checked the app-server surface. thread/unsubscribe only unsubscribes a connection from a thread. thread/archive archives and tears down normal thread state. Neither is exposed as the same operation as AgentControl::close_agent, and neither should be recommended as a slot-release workaround unless it is explicitly wired through the agent close/release path.

Manual mutation/deletion/archive of thread state would risk losing UI/history/evidence and bypassing the only path that currently decrements the multi-agent pool counter.

Requested fix shape

The safest behavior would be to reap finalized spawned agents before enforcing the pool limit, while preserving history/result visibility and using the same release semantics as close_agent/shutdown_live_agent.

If automatic reaping is too risky because Completed can still have live terminals or MCP connections, a narrower fix would be a safe explicit command/API that closes completed agents through AgentControl::close_agent, for example:

  • list open spawned agents with current status and slot-count status,
  • close all finalized spawned agents,
  • close one finalized spawned agent by path/id,
  • return structured errors/timeouts for interrupted or uncloseable agents.

That would avoid relying on the model remembering every close_agent call and would avoid unsafe thread/archive / thread/unsubscribe workarounds.

Tests that would cover this

  • Spawn up to the limit, complete at least one subagent, and verify a later spawn either succeeds after safe reap or returns a recovery surface listing closeable finalized agents.
  • Verify wait_agent completion observation does not silently lose result/history.
  • Verify close_agent remains the path that releases the pool slot.
  • Verify thread/archive and thread/unsubscribe are not treated as equivalent slot release.
  • Verify interrupted/errored subagents either close idempotently or return a bounded structured error instead of hanging the parent turn.
TacoRocket · 1 month ago

I prepared a validated reference patch on a fork. Given the repository contribution policy, this is not an unsolicited PR request; it is included here so maintainers can inspect the proposed behavior concretely if useful:

https://github.com/openai/codex/compare/main...TacoRocket:codex:subagent-finalized-slot-recovery

Patch shape:

  • Reap finalized spawned leaf agents inside AgentControl before retrying agents.max_threads slot reservation.
  • Add model-side close_completed_agents to close every eligible finalized spawned leaf through the same close path.
  • Add app-server/user/hook API agent/closeCompleted with { closedCount } response, so cleanup is not only model-invoked.
  • Preserve history/results; do not use archive/delete/unsubscribe for slot release.
  • Skip running/interrupted agents and finalized parents with live descendants.

Validation in the fork:

  • just fmt
  • just write-app-server-schema
  • cargo test -p codex-core close_completed_agents
  • cargo test -p codex-core finalized
  • cargo test -p codex-core multi_agent_feature_selects_one_agent_tool_family
  • cargo test -p codex-core multi_agent_v2_namespace_is_ignored_without_provider_namespace_support
  • cargo test -p codex-app-server-protocol agent_close_completed
  • cargo test -p codex-app-server-protocol schema_fixtures
  • cargo test -p codex-app-server agent_close_completed

Happy to adapt, split, or submit this as a PR if a Codex maintainer invites that path.

Keesan12 · 1 month ago

One fix shape that helped us on a similar runtime was to separate "thread still exists" from "child is still admitted to run again."

For interrupted children, we force a bounded terminal receipt even if full teardown is still in flight:

  • child_id
  • last_progress_at
  • last_verifier_delta
  • halt_reason
  • next_attempt_allowed
  • resource_reconciliation = pending|done|failed

If teardown cannot finish inside the close path, the child moves to a terminal-but-unreconciled state and stops counting against fresh work admission. A background janitor can then reconcile leftover MCP/process resources without leaving the operator stuck behind a handle that is both dead and still blocking capacity.

That design made two failure modes much easier to reason about for us:

  1. interrupted children stopped poisoning the scheduler forever
  2. operators could see whether they were waiting on real cleanup or on a bookkeeping bug

We've been hitting the same family of issues in MartinLoop, and the biggest improvement came from making the terminal receipt explicit instead of letting "close" be the only source of truth.

vzd3v · 1 month ago

It’s been almost two months since April 24, but it still doesn’t look fixed. Everything is the same in codex-cli 0.140.0

<img width="779" height="360" alt="Image" src="https://github.com/user-attachments/assets/435795e0-f200-4cf0-839a-0e6a568c1903" />

safal207 · 26 days ago

This issue maps closely to LS work on explicit agent lifecycle, execution leases, durable ownership, and replayable cleanup.

The central invariant may be:

agent no longer visible
≠ agent terminated
≠ resources released
≠ launch slot reclaimed

A subagent should remain represented in durable runtime state until it reaches a verified terminal state.

A possible lifecycle record:

{
"schema_version": "subagent-lifecycle/v0.1",
"dispatch_id": "dispatch-184",
"trajectory_id": "repository-task-001",
"parent_agent_id": "agent-root",
"child_agent_id": "agent-review-04",
"runtime_instance_id": "process-7712",
"task_ref": "task:review-auth-module",
"status": "RUNNING",
"lease": {
"owner": "agent-review-04",
"expires_at": "2026-06-24T20:14:00Z",
"last_renewed_at": "2026-06-24T20:09:00Z"
},
"resource_accounting": {
"counts_toward_limit": true
}
}

A useful lifecycle could be:

REQUESTED
→ STARTING
→ RUNNING
→ COMPLETING
→ COMPLETED
→ TERMINATING
→ TERMINATED

with explicit exceptional states:

FAILED
CANCELLED
TIMED_OUT
ORPHANED
TERMINATION_FAILED
UNKNOWN_RUNTIME_STATE

The important boundary is:

«"COMPLETED" describes task outcome, while "TERMINATED" describes runtime cleanup.»

A child may finish its work but still require resource cleanup:

task completed
→ result receipt stored
→ termination requested
→ runtime termination confirmed
→ slot released

The launch counter should be derived from verified live runtime instances, not from stale parent-thread memory.

A reconciliation process could compare:

durable lifecycle registry
↔ runtime process registry
↔ parent-visible agent list
↔ resource-limit accounting

and produce typed findings:

{
"child_agent_id": "agent-review-04",
"registry_status": "RUNNING",
"runtime_status": "NOT_FOUND",
"parent_visibility": "MISSING",
"classification": "ORPHANED_RECORD",
"recommended_action": "RECONCILE_AND_RELEASE_SLOT"
}

The opposite case should also be detected:

{
"child_agent_id": "unknown",
"runtime_instance_id": "process-8891",
"registry_status": "NOT_FOUND",
"runtime_status": "RUNNING",
"classification": "ORPHANED_RUNTIME",
"recommended_action": "QUARANTINE_OR_TERMINATE"
}

Manual lifecycle controls could operate on stable identities:

  • "list_subagents"
  • "inspect_subagent"
  • "request_cancel"
  • "terminate"
  • "force_terminate"
  • "reconcile"
  • "release_stale_slot"

Each control should emit a durable receipt rather than only updating the TUI.

For example:

{
"event_type": "SUBAGENT_TERMINATION",
"child_agent_id": "agent-review-04",
"requested_by": "local-user",
"mode": "FORCE",
"status": "SUCCEEDED",
"runtime_instance_id": "process-7712",
"slot_released": true
}

A deterministic conformance fixture could test:

  1. completed child remains visible until termination is confirmed;
  2. successful termination releases exactly one launch slot;
  3. repeated cleanup is idempotent;
  4. invisible but running runtime is classified as orphaned;
  5. stale registry entry with no runtime releases its slot safely;
  6. partial cleanup reports remaining agents rather than claiming success;
  7. force termination produces an explicit audit event;
  8. parent restart reconstructs the full child registry;
  9. orphaned child cannot later submit an accepted result without identity validation;
  10. resource exhaustion degrades gracefully instead of freezing the root session.

Related LS work:

  • causal audit:

https://github.com/safal207/LS/issues/594

  • execution and evidence gates:

https://github.com/safal207/LS/issues/595

  • deterministic replay:

https://github.com/safal207/LS/issues/597

  • recovered continuation state:

https://github.com/safal207/LS/pull/651

Would a small vendor-neutral "SubagentLifecycleRecord", "TerminationReceipt", and reconciliation fixture help make orphan detection, cleanup, and launch-slot accounting machine-testable?

linerking · 16 days ago

Adding one current user-facing data point from Codex Desktop around the same lifecycle-control gap.

A user ended up with 100+ subagents shown in the sidebar after subagent-heavy work. From the model/tool surface available in the active thread, the agent could only see targeted lifecycle operations such as close_agent(target) and resume_agent(id). It could not enumerate all currently open/live/completed subagents, so it could not safely do the obvious cleanup loop:

list agents -> inspect status -> close completed/inactive agents -> report remaining active agents

This makes the current cleanup model fragile:

  • completed agents can remain open and continue to count toward the subagent/concurrency limit until closed;
  • the parent/model can only close agents whose ids survived in context;
  • after compaction or long sessions, those ids may be unavailable to the model;
  • the sidebar may keep accumulating child-agent/thread entries, which looks like "100+ subagents" to the user;
  • thread archive/sidebar cleanup is not equivalent to runtime close_agent slot/resource cleanup, so users do not know which action is safe.

The missing primitive seems smaller than a full orchestration redesign:

  1. A model/user-facing list_agents or inspect_agents that returns all subagents for the current root/thread tree, including status and whether each counts toward the active limit.
  2. A safe bulk cleanup command such as close_completed_agents / close_inactive_agents that uses the same release path as close_agent.
  3. A bounded/error-returning close_all_agents or force_close_agent path for stale/interrupted agents, so cleanup cannot hang the parent turn indefinitely.
  4. A clear distinction in UI/API between:
  • runtime-close / slot release,
  • completed result/history retention,
  • sidebar archive/hide.

Without this, the user can see the problem in the UI but neither the user nor the parent agent has a reliable supported way to enumerate and clean it up. The workaround becomes manual tracking of spawned ids or database/sidebar cleanup, which is not a safe lifecycle control.

ChunqiGuo02 · 4 days ago

Additional Windows data point: persistent edges remain open while runtime sees no agents

I observed the opposite state-divergence direction from #25426.

Environment:

  • Windows x86_64
  • Parent session created by Codex Desktop / CLI 0.144.0-alpha.4
  • Reproduced and inspected with CLI 0.144.5
  • Multi-agent v1

Observed behavior:

  1. A historical parent session had 18 subagent relationships stored as

open in state_5.sqlite.

  1. All child threads had been inactive for approximately six days.
  2. Resuming the parent with Codex 0.144.5 and requesting closure of all

remaining subagents reported that there were no subagents left to close.

  1. The database still contained all 18 relationships with status = 'open'.
  2. PRAGMA integrity_check returned ok, so this was not SQLite corruption.
  3. After backing up the database, changing only those stale relationships from

open to closed resulted in:

  • zero open spawn edges,
  • database integrity still ok,
  • codex doctor: 0 warnings and 0 failures.

This appears to be durable/runtime lifecycle divergence in the opposite
direction from #25426:

  • durable thread_spawn_edges: open
  • runtime agent registry: no closeable/live agents

Expected behavior:

  • Resume/startup reconciliation should detect open persistent edges whose

runtime agents no longer exist.

  • Such agents should either be reconstructed as closeable agents or safely

transitioned to a terminal state.

  • Users should not need to edit state_5.sqlite manually.

I cannot confirm whether these persistent open rows were counted against
agents.max_threads; I did not observe an explicit agent thread limit
reached
log for this case.

safal207 · 4 days ago

Thanks for the additional data points. These reports make it clear that this is not only a stale-UI issue or a single "close_agent" bug, but a broader lifecycle state divergence problem.

We now have evidence of several inconsistent states:

  • persistent edge is "closed", while the runtime agent still exists and continues to consume a slot;
  • persistent edge is "open", while the runtime no longer has a corresponding agent;
  • the runtime reports no live agent, while the Desktop sidebar still shows the subagent;
  • the parent agent loses subagent IDs after context compaction and cannot enumerate or safely clean them up.

This suggests that "runtime registry", "thread_spawn_edges", and the UI/thread tree are being treated as separate sources of state without a reliable reconciliation step.

A minimal safe fix would likely need:

  1. A bounded and idempotent "close_agent", with a timeout instead of an indefinite wait.
  2. Guaranteed release of the concurrency slot even when the child thread does not terminate cleanly.
  3. Startup/resume reconciliation for persistent "open" edges whose runtime agents no longer exist.
  4. A model/user-facing "list_agents" or "inspect_agents" operation that reports:
  • runtime status;
  • persistent status;
  • terminal state;
  • whether the agent currently counts toward the concurrency limit.
  1. Safe bulk operations such as "close_completed_agents" or "reconcile_agents".
  2. A clear UI distinction between:
  • stopping runtime execution;
  • releasing an agent slot;
  • retaining completed history;
  • archiving or hiding a sidebar entry.

The key requirement is that users should never need to edit "state_5.sqlite" manually, and cleanup should never be able to block the parent turn indefinitely.

A useful regression matrix would include both divergence directions:

  • persistent "closed" + runtime live;
  • persistent "open" + runtime missing;
  • interrupted child with "turn_aborted";
  • repeated "close_agent" calls;
  • resume after restart;
  • parent context compaction;
  • UI state after the runtime handle has disappeared.

This issue appears to be the right umbrella for the lifecycle-control and reconciliation gap, while the narrower issues can document the individual failure paths.