Assistant text blocks intermittently missing from session transcript JSONL (thinking/tool_use persist)

Open 💬 0 comments Opened Jul 4, 2026 by antmid

Summary

Assistant text blocks are sometimes not persisted to the session transcript JSONL, while thinking and tool_use blocks from the same turns are. When a text block is the load-bearing content of a turn (e.g. an announcement a hook or later tooling parses), losing it silently breaks anything that reads the transcript as the source of truth for what the assistant said.

Environment

  • Claude Code 2.1.195
  • Linux
  • Long session (600+ assistant turns), heavy tool use, several compaction boundaries

What I observed

In one session's transcript (~/.claude/projects/<proj>/<uuid>.jsonl), aggregate block counts across all assistant lines:

assistant lines: 601 | thinking blocks: 244 | tool_use blocks: 299 | text blocks: 58

58 text blocks against 244 thinking / 299 tool_use is far below the number of turns in which the assistant emitted user-visible prose. Spot-checking a specific window where I know the assistant emitted a short prose line immediately before a tool call, the transcript shows the turn as thinking + tool_use with no text block — the prose was rendered in the UI but never written to the JSONL.

Representative window (each row is one JSONL entry: index type content-block-types):

977 assistant      thinking
978 assistant      text
979 assistant      tool_use        <- Skill call
...
984 assistant      thinking
985 assistant      thinking
986 assistant      tool_use
989 assistant      tool_use
992 assistant      tool_use
995 assistant      thinking
996 assistant      thinking
997 assistant      tool_use
1005 assistant     tool_use
1009 assistant     thinking
1010 assistant     tool_use

Turns 984–1010 carry thinking/tool_use but the short prose lines the assistant emitted in several of them are absent. Earlier prose in the same session (e.g. index 978) was persisted, so it is intermittent, not a blanket omission. It does not appear to be a late-flush race: re-reading the file well after the session had moved on, the blocks are still absent.

Why it matters

Anything that treats the transcript as the record of what the assistant said can silently get the wrong answer. In my case a PreToolUse hook classifies the session mode by parsing a line-start [SPAD-MODE:…] token from assistant text after a triage tool call; when the announcement text block is dropped, the token is invisible and the hook resolves the wrong mode with no way to recover through the intended (text) channel. I worked around it by having the hook also read the token from the triage tool call's input.args (tool_use blocks persist reliably), but the underlying transcript loss affects any transcript consumer, not just hooks.

Expected

Every assistant text block that was produced in a turn is written to the transcript JSONL, with the same reliability as thinking and tool_use blocks.

Repro notes

I don't have a minimal deterministic repro — it surfaced in a long, tool-heavy session and is intermittent. The signal that reproduces it is the aggregate imbalance: in a session with hundreds of prose-bearing turns, grep-count "type":"text" assistant blocks vs "type":"tool_use" and compare against how often the assistant actually spoke. If text blocks are dramatically undercounted, this is the bug. Happy to share more structural detail (block-type sequences, not content) if useful.

View original on GitHub ↗