Summary
Follow-up to #374. v0.35.4 Batch 1 shipped the Monitor-only subset of the
background-task lifecycle fix: _clear_background_handle gained an is_terminal gate +
_is_terminal_tool_result, so a streaming Monitor no longer drops the
stall_monitor_active_suppressed branch on its first interim tool_result (terminal =
is_error OR the Monitor's timeout_ms deadline; the result text is arbitrary command
stdout so it is deliberately NOT scanned for "completed"/"done" markers).
This issue tracks the general (v2) lifecycle work that was deliberately deferred.
Why it was deferred (the inverse-hang risk)
Making tool_result events non-terminal for Bash(run_in_background), Agent(run_in_background),
ScheduleWakeup, and RemoteTrigger — without a reliable alternative clear path — would
re-introduce the opposite failure: a handle that never clears keeps has_live_background_work()
True forever, so the post-result idle watchdog (#333/#507) never closes stdin → permanent wedge.
A Monitor is safe to defer because its live_monitors deadline ages the handle out; the others
are sets/no-deadline (or fire-later), so they need explicit terminal detection first.
Scope
References
Summary
Follow-up to #374. v0.35.4 Batch 1 shipped the Monitor-only subset of the
background-task lifecycle fix:
_clear_background_handlegained anis_terminalgate +_is_terminal_tool_result, so a streaming Monitor no longer drops thestall_monitor_active_suppressedbranch on its first interimtool_result(terminal =is_errorOR the Monitor'stimeout_msdeadline; the result text is arbitrary commandstdout so it is deliberately NOT scanned for "completed"/"done" markers).
This issue tracks the general (v2) lifecycle work that was deliberately deferred.
Why it was deferred (the inverse-hang risk)
Making
tool_resultevents non-terminal forBash(run_in_background),Agent(run_in_background),ScheduleWakeup, andRemoteTrigger— without a reliable alternative clear path — wouldre-introduce the opposite failure: a handle that never clears keeps
has_live_background_work()True forever, so the post-result idle watchdog (#333/#507) never closes stdin → permanent wedge.
A Monitor is safe to defer because its
live_monitorsdeadline ages the handle out; the othersare sets/no-deadline (or fire-later), so they need explicit terminal detection first.
Scope
live_bg_bashesentry (explicit terminal for bg-Bash).(the only true terminal signal for a backgrounded process — there is no "done" tool_result).
without relying on a tool_result.
non-MCP descendants) when stdin closes. Evidence: the 2026-05-03 comment on background-task lifecycle: clear handle on terminal signal, not first tool_result #374 (a
bulk-reembedpoll loop left 2 zsh shells alive 1h49m after the job finished). OWASP ASI08 (excessive
autonomy) / ASI10 (rogue-agent cleanup).
NOT wedge the post-result idle watchdog.
References
resultevent (idle-but-alive UX gap) #333 / Untether keeps session alive after agent calls ScheduleWakeup outside /loop dynamic mode — wakeup never fires, user must cancel manually #507 / bug(#507 redux): ScheduleWakeup state cleared on tool_result before _post_result_idle_watchdog reads it #544 — post-result idle watchdog + ScheduleWakeup scalar (the machinery this must not break)tool_resultdeltasdocs/plans/2026-05-06-482-bash-streaming-and-monitor-completion.md(Workstream 3)