Transcript: short assistant text between tool calls persists as an empty record (hooks can't read it)

Open 💬 1 comment Opened Jun 16, 2026 by acighi

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

  1. Register a Stop or PreToolUse hook that reads transcript_path and greps the

latest assistant turn's text for a literal string.

  1. Emit that string as a short, standalone assistant message *between two tool

calls* (not as the turn's final message).

  1. 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗