cmux NEXT: the unread dot waits for an agent's background subagents

Merged
AU
6d ago
+978-9

Summary

The unread dot and finished notification wait until an agent’s background agents finish. This covers agent chats and Claude Code hooks in terminal tabs. Background shells such as development servers do not delay completion.

Fixes #25563

A parent finishing with active children keeps its completion pending; the last child releases it once. A new turn supersedes the earlier pending completion. Session and incarnation fences reject stale child hooks, while valid late child completions still release the finish. Terminal teardown clears held children. Notification preparation failures remain retryable.

Hook projection reconciles earlier committed hooks for the same terminal before applying a later hook, including commits whose caller timed out. A failed earlier hook consumes its own retry budget; healthy hooks waiting behind it retain theirs. Permanently failing hooks reach quarantine and stop blocking later work.

Validation

Head: 9bad9f7287afc10c81c429c14abc6b2d63879fd5. Base merged without rewriting branch history. All Rust verification runs on rbx in isolated lane pr25607-attention-dot-a522; Swift compilation runs through cmux-ci.

Regression proof, using the same focused command before and after each repair:

  • cargo test --locked -p cmux-tui-core --lib -- live_hook_catchup_failure: both tests failed at e822f6434769e53387b76eafc50d4c945fac891a because the earlier bad receipt stayed at attempt 1. Both pass with the repair. Covers distinct live arrivals and exact replay, quarantine progress, preservation of healthy retry budgets, and eventual projection.
  • cargo test --locked -p cmux-tui-core --lib -- a_committed_child_start_holds: failed at 8356b13c529931358ef5087507b35843d6c01074; passes after the ordering repair.
  • cargo test --locked -p cmux-tui-core --lib -- a_committed_stop_preserves: failed at a70ab9c8f505666cdd2cdea10a506be638b25ed7; passes after the ordering repair.
  • cargo test --locked -p acpmux --lib -- a_carried_turn_waits_for_its_own_end and cargo test --locked -p cmux-tui-core --lib -- stale_child_hooks_cannot_hold: failed at fa5ac739a58b9b8309c1dddb60b93e3540213f34; pass after repair.
  • cargo test --locked -p cmux-tui-core --lib -- a_child_completion_projected_after: failed at e6ad1ca4b8226e59e079523bc6cfa19f2253c31c; passes after repair.
  • Held-notification retry tests failed at 1342485f819859d97d3ec89bd2b725f39ba27254; pass for child stop, session end, and session start after repair.

Exact-head affected suites and clippy:

cargo test --locked -p cmux-tui-core --lib -- mux::tests::notifications mux::agent_chat_notices mux::tests::agent_reports mux::tests::agent_hook_retry mux::tests::agent_hook_fences journal_reducers
cargo test --locked -p acpmux --lib -- hub::carried_turn
cargo test --locked -p acpmux --test subsession_working
cargo test --locked -p acpmux --test hub_integration -- claude_subagents seen
cargo clippy --locked -p cmux-tui-core -p acpmux --all-targets -- -D warnings

At this exact head, all 120 affected tests pass (103 core, 7 carried-turn, 1 subsession, 9 hub integration), affected-package clippy passes with warnings denied, and the cargo/script fast-tier recipe passes (matrix core, sidebar layout reducer, perf completeness). The lightweight pre-push gate passes with no new violations. The hosted cmux-next fast tier (cargo) check also passed on this head. Exact-head Release app compilation passed through cmux-ci (compile-check --ref 9bad9f7287afc10c81c429c14abc6b2d63879fd5 --app --release --recipe-version 2 --no-cmux-tui, job ab981cb031e677452ed81fb9, 100 s). Rust was verified separately on rbx. Fresh independent correctness review: SHIP.

Observed / Not run

Observed: focused red/green regression results above; all 120 affected tests, clippy, fast-tier, and the pre-push gate pass at this head. Release app compile passed at parent aa3ae270d58a9663b0e686757b676ca0e81e0b7f through cmux-ci job a909e437bb885e0b11d9a6f7 (154 s). The localization audit found no new app string keys; strict full-catalog validation has unchanged base catalog debt.

Not run: full Rust workspace suites, a live Claude background Task in the app, or a new tagged app build for these lifecycle repairs. The earlier tagged build predates the repairs. Re-dogfooding is skipped for the bounded internal retry repair; tests exercise the lifecycle and durable projection behavior directly. Pending completion state is in memory and does not survive a daemon restart.

Check findings

At head 9bad9f7287afc10c81c429c14abc6b2d63879fd5:

  • Windows check: all 35 Unix API diagnostics in unchanged cmux-automations exactly match the messages and locations in the base job.
  • Release-notary: the same two runner-fallback tests fail in the base job. The additional newest-signing-hook test fails because the external glaeda pin is missing from the allowlist. The allowlist and its test are byte-identical to current feat-cmux-next (blob IDs 1192d05186d7020e6809fdbf8afd180285928a8d and 328f200d82c868f7fcc167df3eb5ac6016dc58f9).
  • Windows artifact build: the same 35 error messages and locations as the base, after normalizing Windows path separators.
  • Clippy wrapper: ordinary clippy completes; the global indexing/slicing baseline fails. All 3,498 indexing/string-slice diagnostics were checked against the PR’s added lines; none overlap. Affected-package clippy is verified separately above.

Performance

No perf numbers: this change corrects lifecycle ordering and unread state and claims no performance improvement. The fast-tier recipe provides correctness verification.

Changelog

Fixed: Unread dots and finished notifications wait for an agent’s background work to finish.

Proof

No capture: the dot’s appearance is unchanged; daemon lifecycle tests verify when it appears. Live app timing remains unverified as described above.