Header shows "done"/idle while a harness-owned background Bash task is still running
What happened
When the assistant turn ends (Stop), the terminal header flips to the "done"/idle indicator even while a harness-owned background Bash task (run_in_background: true) is still running and has not yet delivered its return.
Concretely: a turn dispatches a background shell whose completion is expected to re-invoke the agent (e.g. an external code-review/plan-review dispatched with timeout … <reviewer> … under run_in_background: true). The turn ends, the header shows "done", but the session is not actually idle in the meaningful sense — it is waiting for that background job to finish and re-trigger the agent. The indicator therefore reports "done" during a window in which work is still pending.
Why the current behavior is misleading
The harness collapses two distinct states into one idle:
- idle — awaiting the user (nothing pending)
- idle — awaiting a background task the harness owns (a re-invocation is guaranteed to arrive)
From the operator's side these are semantically different: in (2) the session will continue on its own, so "done" is a false signal. There is currently no way to tell them apart from the header.
Why this can't be worked around today
Verified against the hooks documentation and behavior:
- The
Stophook payload contains no field listing active background tasks (session_id,prompt_id,transcript_path,cwd,permission_mode,effort,last_assistant_message). Same for theNotificationhook. Hooks cannot see task state, so they can't gate the indicator. - There is no setting to make the completion/idle indicator conditional on pending background tasks.
Stop→decision: "block"is the only lever that prevents the "done" transition, but it does so by forcing the agent to keep generating — an immediate re-invocation with nothing to do, i.e. a tight loop that burns tokens and pollutes the transcript. It is not passive waiting.- The harness does not distinguish a long-lived service background shell (e.g. a dev server) from a transient one (a review dispatch that exits and re-invokes). Both are opaque "a background task with an ID".
Requested behavior
While there is at least one harness-owned background Bash task still running, suppress the "done"/idle header indicator (or render a distinct "awaiting background job" state) instead of "done".
Refinements worth considering:
- Gate only on background tasks whose completion re-invokes the agent, not on fire-and-forget long-lived services — or expose a per-dispatch flag (e.g.
run_in_background+ anawaits_return/transientmarker) so the caller declares which shells should hold the indicator. - Alternatively (or additionally): expose active background tasks in the
Stop/Notificationhook payload (an array of{id, command, status}) so operators can implement the gating themselves via a hook. This is the lower-effort path and unblocks custom statuslines/notifiers even if the native indicator stays as-is.
Environment
- Platform: WSL2 (Linux)
- Interactive terminal mode
- Background shells dispatched via the
Bashtool withrun_in_background: true+ a shelltimeoutwrapper
Summary
The Stop → idle → "done" transition is decoupled from the background-task lifecycle and cannot be made task-aware from user space (no task context in any hook payload, no setting). Either make the native indicator background-task-aware, or surface active background tasks in hook payloads so it can be gated externally.