openai / openai/codex

[App Server] stale watched_status overrides completed subagent state during thread enrichment

Open
#37,916 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app app-server bug subagent
Dominant language
Rust
Stars
125k
Forks
19.5k
PR merge metrics
PR metrics pending

Description

What version of the Codex App are you using (From “About Codex” dialog)?

Originally reproduced on 26.803.41515 (build 6321) with bundled codex-cli 0.147.0-alpha.6.5. The current installation is 26.803.61601 (build 6396) with the same bundled CLI version. The relevant reconciliation code is also unchanged on current main at 070a26a1f00817931a17e2cdf8fbe03a2a0ed128 (2026-08-11).

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.2.0 arm64 arm

What issue are you seeing?

Codex Desktop can keep a completed subagent under Active / Working, even after the child emitted a final answer and task_complete and the parent received the result.

The source contains a deterministic status-precedence problem in enrich_loaded_threads:

  1. A cached watched_status is copied into thread.status first.
  2. The loaded child then reports AgentStatus::Completed(_) or Interrupted.
  3. That authoritative status changes the thread to Idle only when watched_status.is_none().

Therefore this input remains incorrectly active:

watched_status = Active
loaded agent_status = Completed(...)
enriched thread.status = Active   # current result

This matches the observed Desktop behavior: opening a completed child can refresh it into Done, while an unopened child remains Working indefinitely.

This report is intentionally narrower than the existing UI reports: it identifies one concrete reconciliation branch that lets a stale watch-cache value override the loaded agent's terminal state.

What steps can reproduce the bug?

UI reproduction:

  1. Start a parent task that spawns one or more subagents.
  2. Let a child return its final answer and emit task_complete.
  3. Do not open the child's detail view.
  4. Open the parent task's Subagents panel.
  5. Observe that the terminal child can remain under Active / Working.
  6. Open that child and navigate back. Its card may then move to Done, indicating that navigation forced a refresh that normal enrichment missed.

Deterministic regression-test shape:

  1. Provide enrich_loaded_threads a spawned-child thread whose watch manager returns ThreadStatus::Active.
  2. Make thread_manager.get_thread(...).agent_status() return AgentStatus::Completed(...).
  3. Assert that the enriched status is not Active / Working.

The current implementation leaves it Active because the terminal branch is guarded by watched_status.is_none().

What is the expected behavior?
  • A loaded child whose authoritative agent status is terminal must not be presented as actively Working merely because the watch cache still contains Active.
  • The enrichment path should reconcile freshness or let a terminal loaded status override stale Active state.
  • Add a regression test for watched Active + loaded Completed and the equivalent interrupted/error paths.
  • Opening the child detail view should not be required to move it out of Active.

An open spawn edge may intentionally mean that a completed child is resumable. This report is not requesting that every task_complete automatically close or archive the edge. It is requesting that resumable/idle history not be classified as actively executing.

Additional information

Related symptom reports:

  • #35209 — completed subagents remain Active after task_complete
  • #37563 — terminal/closed children rehydrate as Working after restart
  • #37729 — completed children move only after their results are opened
  • #23930 — closed/readback-missing agents remain visible

Current Multi-Agent V2 does not expose the V1 close_agent lifecycle tool, so users cannot reliably repair this state from the parent task. A local diagnostic forced the same model through V1 and verified that explicit close transitions the persisted child edge to closed; that avoids accumulation but is a compatibility workaround, not a product fix.

No database was modified. State inspection was read-only. No thread IDs, prompts, project names, repository paths, screenshots, credentials, or private task content are included.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in codex-rs/app-server/src/request_processors/thread_enrichment.rs, especially enrich_loaded_threads and the watched_status guard. Trace how loaded AgentStatus::Completed and Interrupted are reconciled with cached ThreadStatus::Active, then add regression coverage for completed, interrupted, and error cases. Done means a terminal loaded child is no longer presented as actively Working without opening its detail view.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.