When Claude Code runs an agent or shell command in the background (run_in_background=true), the eventual completion arrives in the JSONL as a type: user entry whose message.content is a string beginning with <task-notification> — not the usual list of tool_result content blocks. The transcript renderer treats these as ordinary user prompts (blue "User" panel), which makes long sessions look like the human kept typing system-generated XML at random intervals.
In a recent session of mine this affected 7 entries: 1 background Agent completion, 3 Monitor events, and 3 background Bash completions.
Minimal repro JSONL (4 lines — last entry is the bug):
{"type":"assistant","timestamp":"2026-01-01T10:00:00.000Z","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_AAA","name":"Bash","input":{"command":"sleep 30","run_in_background":true,"description":"Long task"}}]}}
{"type":"user","timestamp":"2026-01-01T10:00:00.500Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_AAA","content":"Async bash launched. id=bash_AAA"}]}}
{"type":"assistant","timestamp":"2026-01-01T10:00:01.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Started; continuing while it runs."}]}}
{"type":"user","timestamp":"2026-01-01T10:00:31.000Z","message":{"role":"user","content":"<task-notification>\n<task-id>bash_AAA</task-id>\n<tool-use-id>toolu_AAA</tool-use-id>\n<status>completed</status>\n<summary>Background command \"Long task\" completed (exit code 0)</summary>\n</task-notification>"}}
Save as repro.jsonl and run:
claude-code-transcripts json repro.jsonl -o /tmp/out
Observed: the 4th entry renders as
<div class="message user" id="msg-..."><span class="role-label">User</span> ...
<p><task-notification> ... </task-notification></p>
i.e. a normal blue user-prompt bubble.
Expected: classified as a system / tool-reply panel (e.g. orange tool-reply), since the human did not author it. Ideally the renderer would parse out <status> and <summary> (and <result> when present) for a compact display.
Suggested detection: any type: "user" entry where message.content is a string starting with <task-notification>. (These are also distinguishable by the absence of a tool_result content block and the presence of an <output-file> or <task-id> element inside.)
Environment: claude-code-transcripts (current uvx install), Claude Code generating the JSONL.
When Claude Code runs an agent or shell command in the background (
run_in_background=true), the eventual completion arrives in the JSONL as atype: userentry whosemessage.contentis a string beginning with<task-notification>— not the usual list oftool_resultcontent blocks. The transcript renderer treats these as ordinary user prompts (blue "User" panel), which makes long sessions look like the human kept typing system-generated XML at random intervals.In a recent session of mine this affected 7 entries: 1 background
Agentcompletion, 3Monitorevents, and 3 backgroundBashcompletions.Minimal repro JSONL (4 lines — last entry is the bug):
{"type":"assistant","timestamp":"2026-01-01T10:00:00.000Z","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_AAA","name":"Bash","input":{"command":"sleep 30","run_in_background":true,"description":"Long task"}}]}} {"type":"user","timestamp":"2026-01-01T10:00:00.500Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_AAA","content":"Async bash launched. id=bash_AAA"}]}} {"type":"assistant","timestamp":"2026-01-01T10:00:01.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Started; continuing while it runs."}]}} {"type":"user","timestamp":"2026-01-01T10:00:31.000Z","message":{"role":"user","content":"<task-notification>\n<task-id>bash_AAA</task-id>\n<tool-use-id>toolu_AAA</tool-use-id>\n<status>completed</status>\n<summary>Background command \"Long task\" completed (exit code 0)</summary>\n</task-notification>"}}Save as
repro.jsonland run:Observed: the 4th entry renders as
i.e. a normal blue user-prompt bubble.
Expected: classified as a system / tool-reply panel (e.g. orange
tool-reply), since the human did not author it. Ideally the renderer would parse out<status>and<summary>(and<result>when present) for a compact display.Suggested detection: any
type: "user"entry wheremessage.contentis a string starting with<task-notification>. (These are also distinguishable by the absence of atool_resultcontent block and the presence of an<output-file>or<task-id>element inside.)Environment:
claude-code-transcripts(currentuvxinstall), Claude Code generating the JSONL.