[BUG] Headless (-p / stream-json): mid-turn task notifications and hook attachments never reach the model
Summary
In headless sessions (claude -p / --input-format stream-json, entrypoint sdk-cli), a <task-notification> queued while a turn is running never reaches the model. The transcript records it as a queued_command attachment with a rendered <system-reminder>, parented on the next tool_result, but the model's next request does not contain it. A notification queued while the session is idle arrives fine (as a user message).
Hook output that becomes an attachment is lost the same way: hookSpecificOutput.additionalContext (hook_additional_context) and exit 2 / decision: "block" (hook_blocking_error).
Environment
- Claude Code 2.1.296 (native binary), Linux
- Entrypoint
sdk-cli: both plainclaude -pand an orchestrator drivingclaude --input-format stream-json --output-format stream-json
Repro
No user settings or hooks loaded:
mkdir -p /tmp/tnprobe && cd /tmp/tnprobe
claude -p --setting-sources local --permission-mode bypassPermissions --output-format json \
"Step 1: start this with the Bash tool and run_in_background true: \`sleep 2; echo done\`. Step 2: run foreground Bash \`sleep 5\`. Step 3: Read /etc/hostname. Step 4: Read /etc/os-release. Do NOT read the background task's output file. Final answer: did any <task-notification> or system-reminder about the background task appear in your context so far? Answer YES or NO." < /dev/null
Result: NO.
Transcript sequence (~/.claude/projects/-tmp-tnprobe/<session>.jsonl):
queue-operation enqueue <task-notification>…
user tool_result
queue-operation remove
attachment queued_command (commandMode "task-notification", rendered <system-reminder>[SYSTEM NOTIFICATION - NOT USER INPUT]…)
assistant "NO. No <task-notification> … has appeared"
The same happens in a long-running stream-json session: the queued_command attachment is parented on a Read tool_result, and that result reaches the model with no system-reminder added. Across one machine's local transcripts, 672 of 1024 task-notification enqueues took this path (enqueue → remove → attachment) rather than the idle path (enqueue → dequeue → user message).
Expected
The rendered queued_command attachment reaches the model with the next request, as the transcript says. Same for hook_additional_context and hook_blocking_error.
Impact
Agents in headless sessions never learn that background Bash, Monitor events or background subagents finished, unless the turn happens to end first. They end turns waiting for work that is already done, or miss failures. Hook additionalContext is unusable in headless mode for the same reason.
Workaround
A PostToolUse hook on Bash scans the transcript for task-notification enqueues not yet delivered as user messages and appends them via hookSpecificOutput.updatedToolOutput, which does reach the model. It misses notifications queued after the turn's last Bash call.
Related
- #97575:
queued_commandattachments reach the model in interactive sessions but are mistaken for tool output. Here, in headless sessions, they don't reach it at all. - #77300: notifications don't wake an idle agent-teams teammate.