Transcript: short assistant text between tool calls persists as an empty record (hooks can't read it)
Summary
A short assistant text message emitted between two tool calls is written to
the session transcript JSONL as an effectively empty record (a thinking-type
entry with no readable text block) — what looks like a separate "narration"
channel. Assistant messages that ride this channel are invisible to hooks that
read transcript_path and grep assistant text.
Environment
- Claude Code 2.1.170
- Model: Fable 5 (also observed on Opus 4.x)
- macOS
Impact
Hooks (Stop, PreToolUse) that detect a user-visible marker in assistant chat
output by grepping the transcript get false negatives: the marker was shown
to the user but never persisted as readable text, so the hook can't see it.
This breaks any "require X to appear in the reply before allowing Y" pattern.
Workaround we use: carry the marker in the next tool call's description
field, since tool_use records always persist with their content.
Repro
- Register a Stop or PreToolUse hook that reads
transcript_pathand greps the
latest assistant turn's text for a literal string.
- Emit that string as a short, standalone assistant message *between two tool
calls* (not as the turn's final message).
- Inspect the transcript JSONL: the record for that message has no
text
content block (thinking-type / "narration"); the hook does not find the
string.
Expected
Assistant text that is displayed to the user should persist in the transcript
with its text content, so transcript-reading hooks can observe it — regardless
of whether it sits between tool calls or at the end of a turn.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗