TUI return can leave stale busy spinner and active-turn inhibitor after subagent completion on Linux/tmux

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

What version of Codex CLI is running?

v0.118.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.4 high

What platform is your computer?

Linux x86_64

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

tmux

What issue are you seeing?

I hit a bad TUI/runtime state around subagent switching.

The important operator action is that I was switching between the subagent and the main agent via /agents.

After the child subagent completed, the parent/main thread ended up in a stale busy-looking state instead of cleanly recovering.

From the user side this looked like the app hung or crashed while leaving the screen in a state that still suggested work was ongoing.

What I observed locally on the affected tmux pane:

  • the pane content still contained repeated Waited for background terminal lines from the earlier work
  • the final assistant response from the parent thread was already printed
  • the normal prompt was visible again at the bottom
  • but the tmux pane title remained stuck at ⠧ home (busy spinner title) and did not clear
  • the title stayed exactly ⠧ home across repeated checks over multiple seconds, instead of returning to a non-busy title
  • the Codex process remained alive
  • a stale child process was still present:
  • systemd-inhibit --what=idle --mode=block --who codex --why "Codex is running an active turn" -- sleep 2147483647
  • that inhibitor was still alive more than an hour after the turn had already completed

So even if the backend believes the turn finished, the TUI/runtime busy-state cleanup did not happen correctly.

This is not just “the turn completed and I did not want it to” (although from the operator side it did feel wrong). The concrete bug is that the CLI was left in a contradictory state:

  • visible prompt is back
  • parent turn has completed
  • but busy indicators and the active-turn idle inhibitor are still stuck as if a turn were running

What steps can reproduce the bug?

I do not yet have a minimal deterministic repro, but the sequence that triggered it was:

  1. Start Codex CLI in tmux.
  2. Use a workflow that spawns/uses a subagent.
  3. Switch between the child and main thread via /agents.
  4. Switch back to the main thread and wait for the child subagent to complete.
  5. Observe that after completion, the main thread can be left in a stale state:
  • the screen still looks like it was working recently (Waited for background terminal still visible)
  • the tmux pane title remains stuck on a busy spinner
  • an active turn systemd-inhibit child remains alive
  • the process is alive, but the session no longer feels cleanly interactive from the user perspective

Affected top-level thread ID:

  • 019d61b9-5b70-78e2-9a11-56ebcfd12bba

Child subagent completion was delivered into that parent thread via <subagent_notification>.

What is the expected behavior?

After subagent completion and return to the main thread, Codex should do one of these consistently:

  • keep the parent turn active and visibly keep waiting, or
  • complete the parent turn and fully clear all busy state

It should not leave the session in a contradictory half-finished state where:

  • the prompt is back,
  • the backend turn is complete,
  • but the busy spinner/title and the Codex is running an active turn inhibitor are still stuck.

Additional information

Concrete local evidence from this incident:

  • parent thread rollout recorded task_complete at 2026-04-06T10:55:05.491Z
  • affected tmux pane title remained:
  • ⠧ home
  • repeated checks over ~6 seconds showed the title was still unchanged:
  • t0: ⠧ home
  • t+3s: ⠧ home
  • t+6s: ⠧ home
  • the stale inhibitor process remained present long after completion:
systemd-inhibit --what=idle --mode=block --who codex --why Codex is running an active turn -- sleep 2147483647

Related but not identical issues:

  • #11472 (sub-agent shutdown / parent restoration)
  • #12033 / #14303 / #14314 (stale Waiting/Waited for background terminal state)
  • #12645 (tmux TUI/input stuck after completion)

My current guess is that this is another turn-lifecycle cleanup bug in the TUI/runtime layer, possibly triggered specifically by the /agents thread-switch path around subagent completion boundaries.

View original on GitHub ↗

12 Comments

github-actions[bot] contributor · 3 months ago

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

  • #15920
  • #16386
  • #15723

Powered by Codex Action

ybratec · 3 months ago

Duplicate comment from the wrong account. Reposted above from iqdoctor.

ybratec · 3 months ago

Duplicate comment from the wrong account. Reposted above from iqdoctor.

ybratec · 3 months ago

Duplicate comment from the wrong account. Reposted above from iqdoctor.

ybratec · 3 months ago

Duplicate comment from the wrong account. Reposted above from iqdoctor.

ybratec · 3 months ago

Duplicate comment from the wrong account. Reposted above from iqdoctor.

iqdoctor · 3 months ago

Carrying over the concrete findings from my follow-up analysis, since this issue currently has the user report but not the code-level hypothesis.

I dug through the upstream TUI thread-switch path and there is a plausible logic hole around /agents restores:

  • codex-rs/tui/src/app.rs
  • store_active_thread_receiver() saves store.input_state = self.chat_widget.capture_thread_input_state() when leaving the active thread.
  • ThreadEventStore::push_notification() clears active_turn_id on TurnCompleted, but it does not sanitize the already-saved input_state for that inactive thread.
  • codex-rs/tui/src/chatwidget.rs
  • capture_thread_input_state() persists both:
  • task_running: self.bottom_pane.is_task_running()
  • agent_turn_running: self.agent_turn_running
  • restore_thread_input_state() later restores self.agent_turn_running = input_state.agent_turn_running and immediately does:
  • self.turn_sleep_inhibitor.set_turn_running(self.agent_turn_running)
  • self.update_task_running_state()

That creates this failure mode:

  1. Main thread is running.
  2. User switches away via /agents.
  3. TUI snapshots agent_turn_running=true / task_running=true.
  4. While the thread is inactive, TurnCompleted arrives.
  5. The store clears only active_turn_id, but the saved widget input_state still says the thread is running.
  6. When switching back, snapshot restore runs before the live queue is fully reconciled.
  7. That can resurrect the busy UI state and Linux systemd-inhibit child from stale snapshot state.

That lines up with the exact symptom cluster here:

  • prompt is back
  • parent turn is already complete
  • but busy/title state is still stuck
  • and systemd-inhibit --why "Codex is running an active turn" remains alive

I also noticed the restore path appears to replay snapshot state before draining queued live events, which widens the race window further.

One more version note for attribution: the runtime artifact involved in the bug really was @openai/codex 0.118.0 (/usr/bin/codex -> /usr/lib/node_modules/@openai/codex/bin/codex.js, and codex --version reports 0.118.0).

So my current best guess is that the fix is in one of these directions:

  • do not treat persisted agent_turn_running / task_running snapshot flags as authoritative across /agents thread switches,
  • or sanitize saved input_state whenever an inactive thread receives TurnCompleted / ThreadClosed,
  • or derive restored running state from canonical turn lifecycle state instead of raw saved widget state.
iqdoctor · 3 months ago

Checked again against the newer prerelease line after upgrading the runtime here to @openai/codex 0.119.0-alpha.11 and syncing a local source checkout to the matching tag rust-v0.119.0-alpha.11.

Current read after source inspection: I do not see evidence that this specific stale-restore bug is fixed in 0.119.0-alpha.11.

The same critical path is still present in the rust-v0.119.0-alpha.11 source tree:

  • codex-rs/tui/src/app.rs
  • ThreadEventStore::push_notification() still clears active_turn_id on TurnCompleted, but does not sanitize saved input_state
  • replay_thread_snapshot() still restores snapshot.input_state before replaying turns/events
  • codex-rs/tui/src/chatwidget.rs
  • capture_thread_input_state() still persists:
  • task_running: self.bottom_pane.is_task_running()
  • agent_turn_running: self.agent_turn_running
  • restore_thread_input_state() still restores self.agent_turn_running = input_state.agent_turn_running and immediately re-arms:
  • self.turn_sleep_inhibitor.set_turn_running(self.agent_turn_running)
  • self.update_task_running_state()

So the stale snapshot / replay ordering hole still appears to exist in this newer alpha.

I also compared rust-v0.119.0-alpha.3 .. rust-v0.119.0-alpha.11 and did not find changes in the obvious /agents-selection files (multi_agents.rs, agent_picker.rs, agent_shortcuts.rs). The main TUI changes in that range touched app.rs and chatwidget.rs, but not in a way that obviously closes this restore-order bug.

I attempted one targeted codex-tui test run on the synced alpha.11 source, but local compilation aborted because the machine is currently out of disk space (No space left on device), so this latest-alpha check is source-level rather than runtime-test-backed.

Net: as of 0.119.0-alpha.11, I would still treat this issue as likely open unless there is a fix elsewhere that I missed.

iqdoctor · 3 months ago

One more useful detail from the review before I collapse the duplicate:

There is already a unit test in the TUI suite that encodes part of the problematic behavior:

  • codex-rs/tui/src/chatwidget/tests/composer_submission.rs
  • restore_thread_input_state_syncs_sleep_inhibitor_state

That test explicitly asserts that restoring a snapshot with agent_turn_running=true should re-enable the sleep inhibitor and keep task-running state.

So at least part of the stale-state restoration behavior is currently intentional at the unit-test level, not just an accidental side effect.

What I still did not find is a regression test for the important opposite case:

  • switch away via /agents
  • thread completes while inactive
  • switch back
  • busy state / inhibitor must stay cleared

I think that missing regression test is part of why this bug path could survive across releases.

iqdoctor · 3 months ago

Version matrix / review scope for this report, to make attribution clearer:

Versions actually involved in my investigation

Runtime artifact when the bug was observed

  • installed CLI at incident time: @openai/codex 0.118.0
  • verified from:
  • /usr/bin/codex -> /usr/lib/node_modules/@openai/codex/bin/codex.js
  • codex --version reported 0.118.0

Source trees I reviewed

  1. local checkout HEAD at the time of the first source inspection:
  • commit c1d18ceb6f22ae3acd67bbd6badad0f475b31dfc
  • workspace version there was 0.0.0
  • this was used only for code-path inspection, not as the claimed runtime version
  1. nearest locally available 0.118.x release-tagged tree I compared against:
  • rust-v0.118.0-alpha.3
  • commit a5ea426b9a867ec64a41fa2d956bae552036cd4d
  1. later prerelease re-check after upgrading the installed runtime:
  • installed CLI upgraded to @openai/codex 0.119.0-alpha.11
  • local source checkout synced to matching tag rust-v0.119.0-alpha.11
  • commit e169c915824307eef6c175b7f28fb381da853ef0

Current read on related issues

This issue seems to sit at the intersection of two existing bug families rather than being a perfect duplicate of any single older report:

  • /agents / parent-thread restoration family:
  • #11472 is very close in spirit: switching to a sub-agent view and then mishandling what happens when that child thread ends
  • async completion / wake-up / lifecycle family:
  • #15723 = background subprocesses/subagents do not wake the caller on completion
  • #16386 = after reconnect, parent no longer resumes when child completes
  • #15920 = sub-agent completion can improperly terminate the parent turn
  • #14318 = older race around async subagent notification landing in the wrong parent lifecycle context
  • stale background-terminal / stale-wait-state family:
  • #12033, #14303, #14314, #15461
  • tmux post-completion stuck-input/TUI family:
  • #12645

So my current best framing is:

  • #16904 is not just another generic background-terminal issue,
  • and not just another generic subagent wake-up issue,
  • but rather a /agents thread-switch + subagent-completion lifecycle bug that can leave the TUI in a contradictory restored state after the parent turn has already completed.

If that framing is right, the most useful regression coverage would likely be an integration test that combines:

  • active parent turn
  • switch away via /agents
  • child/subagent completion while parent thread is inactive
  • switch back
  • assert no stale busy state / inhibitor / phantom running flags remain.
iqdoctor · 3 months ago

Short triage note after checking the nearby reports:

This does not look like a clean duplicate of any single older issue.

  • #15920 is the closest match for the "subagent completion can improperly end the parent turn" part.
  • #11472 is the closest match for the /agents thread-restore path.
  • #14318 is the closest older race around async subagent notification vs parent turn lifecycle.
  • #14314 / #12645 are the closest symptom family for stale waiting/busy TUI state after completion.

What seems specific here is the combination:

  • switch away via /agents
  • child completes while the parent thread is inactive
  • parent turn is already complete
  • switching back can still resurrect stale busy UI + the active-turn sleep inhibitor

So this currently looks more like an intersection bug across /agents restore logic and async subagent-completion cleanup than just a generic background-terminal issue.

I also re-checked the relevant TUI source path against 0.119.0-alpha.11 and still do not see obvious evidence that this specific restore-order / stale-snapshot path is fixed there.

Don-Yin · 2 months ago

Related macOS/Kitty report filed at #19331.

The symptom I saw is a similar stale TUI/busy-state family, but without tmux/subagent switching as the main trigger: Kitty still had a live Codex process, while the rollout JSONL had already ended with event_msg.task_complete, and the original TUI was non-reactive. Environment: Codex CLI 0.124.0, macOS 26.4.1 arm64, Kitty 0.46.1.