Skip to content

Stream background-command output into claude native mirrors - #1255

Merged
edwin-zvs merged 1 commit into
mainfrom
native-bg-task-transcript
Aug 16, 2026
Merged

Stream background-command output into claude native mirrors#1255
edwin-zvs merged 1 commit into
mainfrom
native-bg-task-transcript

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Problem

Selecting a claude background-task "native" subagent session in the TUI (and webui) session list opened an empty session view. Task-tool subagent mirrors already stream their transcript (projected from subagents/agent-*.jsonl), but background Bash tasks were projected with lifecycle state and title only — no transcript source was wired, so their mirrors had zero events. Since long-running background tasks are what actually stays visible/running in the session list, the empty view is what users hit.

Fix

Claude Code announces each background task's output file in the launch tool_result ("Output is being written to: …/tasks/<id>.output"). The claude adapter now:

  • captures the launching Bash command and the announced output path at launch
  • tails the output file every watcher tick, projecting into the task's mirror:
    • the command as an opening user message (ordinal 0)
    • output as line-bounded tool messages whose ordinal is the 1-based index of the chunk's last line — deterministic at line granularity, matching the daemon's ordinal replay-dedupe contract
  • holds a trailing partial line until its newline arrives (no line split across events), and flushes everything — partial included — when the terminal notification lands
  • caps per-tick chunks at 256 KiB so a fast writer drains across ticks instead of producing one enormous event

No daemon or UI changes needed: the existing native-mirror projection persists + broadcasts these events, so the transcript is live in the TUI Chat view and webui.

Latent reconcile bug fixed alongside

The NativeSubagentSnapshot retained-set was derived only from subagents/ dir files, so any snapshot change (an agent child appearing/finishing) would archive a still-running background task's mirror. Live background task ids now count as retained, and a transient read_dir failure no longer emits an authoritative snapshot (which could archive mirrors off bad data). A consequence: the first tick now emits a snapshot even with no children, so stale "running" background mirrors from before a daemon restart get archived on resume — previously they lingered as zombies (the underlying process died with the harness).

Spec 0079 updated with the background-command transcript + retained-set rules.

Verification

  • Unit tests: launch→tail registration, output-path parsing (string + array tool_result content), streaming/partial-hold/terminal-flush lifecycle, liveness tracking.
  • Full workspace suite: 2715 passed, 0 failed.
  • End-to-end against an isolated daemon built from this branch (stub claude, hand-written native transcript): mirror shows the command message, output streams live, the partial line is held until its newline, and the completion notification flushes it and archives the mirror:
--- mirror transcript after first output ---
state: running archived: False
1 {"role": "user", "text": "while sleep 1; do date; done", "type": "message"}
2 {"role": "tool", "text": "tick one\ntick two", "type": "message"}
--- after partial (partial must NOT appear yet) ---
3 {"role": "tool", "text": "tick three", "type": "message"}
--- after completion ---
state: done archived: True title: Tick the clock
4 {"role": "tool", "text": "partial-held", "type": "message"}

Known limitation

On daemon restart + session resume, the root transcript history is skipped, so pre-restart background launches are not re-discovered and their output is not re-tailed — accepted, since the background process itself does not survive the harness restart (its mirror is now correctly archived by the first snapshot instead of lingering as running).

🤖 Generated with Claude Code

Background Bash tasks projected as native subagent mirrors had no
transcript source: selecting one in the TUI/webui session list opened an
empty session view. Claude announces each background task's output file
in the launch tool_result, so the adapter now:

- captures the launching command and the announced output path at launch
- tails the output file every watcher tick, projecting the command as an
  opening user message (ordinal 0) and output as line-bounded tool-role
  chunks whose ordinal is the 1-based index of the chunk's last line
- holds a trailing partial line until its newline arrives, and flushes
  everything (partial included) when the terminal notification lands
- caps per-tick chunks at 256 KiB so fast writers drain across ticks

Also fixes a latent reconcile bug: the retained-child snapshot was
derived only from subagents-dir files, so any snapshot change would
archive still-running background-task mirrors. Live background task ids
now count as retained, and a transient read_dir failure no longer emits
an authoritative snapshot.
@edwin-zvs
edwin-zvs merged commit 4379cbf into main Aug 16, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the native-bg-task-transcript branch August 16, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant