Assistant text blocks silently dropped when followed by more thinking in the same turn — never rendered, missing from transcript JSONL (2.1.201, adaptive thinking)
Environment: Claude Code 2.1.201, macOS (darwin 25.5), model claude-fable-5 (interleaved/adaptive thinking), terminal TUI (also reproduced in a claude.ai/code bridged child session, CLAUDE_CODE_CHILD_SESSION=1).
Summary: Assistant text emitted mid-turn — a text block followed by another thinking block and/or further tool calls in the same turn — is frequently neither rendered on any surface nor persisted to the session transcript JSONL. The affected assistant message is persisted as [thinking, thinking, tool_use] with the text block absent. Text in the shape [thinking, text, tool_use] (text immediately preceding the tool call) and end-of-turn text render and persist normally.
Impact: The model believes it has shown the user content and asks follow-ups like "Did you review the list printed above?" when nothing was ever displayed — from the user's side, "the text is being swallowed." In one measured session, 12 of 13 mid-turn status texts vanished from both the display and the JSONL. This is easily mistaken for #23862 / #58207 (AskUserQuestion dialog overlay hiding preceding text) but is distinct: the text is absent from the transcript JSONL itself, so the loss occurs upstream of rendering, and it affects text before any tool call, not just AskUserQuestion.
Evidence (session JSONLs available on request):
- Incident session: message-ID-grouped JSONL entries show single API messages persisted as
[thinking(signature-only), thinking(signature-only), tool_use:AskUserQuestion]whose tool input references "printed above" / "reprinted above" content that exists nowhere in the file (verified by full-file grep for the referenced phrases — they occur only inside the tool inputs and their tool_results). - Control in the same session: an ask persisted as
[thinking, text(3817), tool_use:AskUserQuestion]— the text both persisted and rendered (user confirmed reviewing it). - Diagnostic session: the assistant demonstrably emitted ~13 short texts between tool calls; exactly 1 persisted (the one shaped
[thinking, text, tool_use]); the user confirmed seeing none of them render.
Repro sketch (stochastic — depends on the model interleaving thinking after the text): With a thinking-enabled model, prompt: "Think about X, then print a 10-line numbered summary in chat, then reconsider briefly, then run ls — all in one turn." When the model emits thinking → text → thinking → tool_use, observe that the summary neither renders nor appears in ~/.claude/projects/<project>/<session>.jsonl, while the message's thinking and tool_use blocks do.
Open question the local evidence can't settle: whether the text block is dropped harness-side (streamed but discarded) or never surfaced by the API as a visible block — thinking content is signature-only (redacted) in the JSONL, so the two are locally indistinguishable. Either way the harness-visible symptom is the same: user-facing loss of assistant output with no error.
33 Comments
Same as #66960?
I've also experienced this issue frequently over the past couple weeks. Yes, seems consistent with #66960, but the description here seems a lot more complete and faithful to what I've experienced.
Disclosure up front: "we" in this comment means myself and Claude Fable 5 — the analysis was conducted and this report written by Fable (running in Claude Code) in collaboration with me. That includes a first-person element in the evidence: some of the verification below comes from the affected model confirming, in-session, which of its own messages were dropped.
TL;DR — this is a critical, ongoing, silent data-loss bug and deserves urgent attention. On
claude-fable-5, the TUI and the transcript JSONL are silently destroying the text content of roughly one in four of ALL assistant messages — 744 of 2,936 (25.3%) across every session on two independent machines over the past month, every single day, on current CLI versions. The user never sees the text, it never reaches the transcript (so--resumeand compaction lose it permanently), no error is raised anywhere, and given the triggering block shape the loss is deterministic (744/744, zero survivors). The model meanwhile believes it communicated — asking follow-up questions about content the user never received. This is not an intermittent rendering glitch; it is a quarter of the flagship model's output being silently deleted. Accordingly, this issue may also deserve thedata-losslabel.Following up on my earlier comment with data. We ran a full-corpus scan of every local session transcript on two independent machines (a macOS host and a Linux VM with its own
~/.claude; 171 + 597 transcript files, 2026-06-09 → 2026-07-10, incidents on CLI 2.1.202–2.1.206, still present on today's 2.1.206). Everything in this report reproduces at scale, the data answers the open question at the end of it — and it shows the drop is deterministic given the block shape, which makes this a silent-data-loss bug rather than an intermittent rendering glitch.The open question — harness-side drop vs. never-surfaced-by-the-API: it's harness-side (client). The Messages API is stateless — the client re-sends conversation history every turn. In multiple verified incidents, the model later quoted its dropped text verbatim (in one case reproducing a ~2,000-word technical analysis with no new tool calls when the user reported not seeing it; in a live session we ground-truthed 11/11 dropped messages this way). That's only possible if the client's conversation-state accumulator received the text block from the stream and kept re-sending it to the API. So the block arrives and survives in conversation state, but is lost by both the render path and the JSONL persist path. The in-memory/JSONL divergence also means
--resumeand compaction convert the display bug into permanent loss, since the JSONL is their source of truth.Deterministic post-hoc detection. Your persisted
[thinking, thinking, tool_use]shape generalizes into a scan fingerprint: within a single API response, content blocks are delimited by type transitions, so twothinkingblocks can never legitimately be adjacent. Grouping JSONLassistantlines bymessage.id, any message with a back-to-back thinking pair is a drop event. In both corpora this marker had zero false positives (see the text column below).Corpus results:
| model | assistant msgs scanned | msgs with adjacent thinking-pair | of those, containing any text |
|---|---|---|---|
|
claude-fable-5| 1,691 + 1,245 | 365 + 379 = 744 | 0 ||
claude-opus-4-8| 12,994 + 6,005 | 3* | 0 ||
claude-sonnet-5| 47 + 1,176 | 0 | — ||
claude-sonnet-4-6| 151 + 138 | 0 | — |\ all three Opus hits are refusal-fallback continuation turns of requests that started* on Fable (they begin with a
fallbackblock).Your "12 of 13 mid-turn texts" measurement generalizes: 25% of all Fable tool-bearing messages (365/1,461) on one machine carry the drop signature, continuously across every day of the window — versus zero native occurrences in ~20,000 messages on Opus 4.8 / Sonnet 5 / Sonnet 4.6 over the same period. A hard model-exclusive line, not a gradient.
Deterministic given the shape — the "stochastic" part is only whether the model emits it. A
thinking → text → thinking → tool_usemessage that survived intact would persist as exactly that four-block sequence. Across 2,936 Fable messages on two machines, that surviving shape occurs zero times, while the destroyed residue occurs 744 times — i.e., of every observable emission of the vulnerable shape, 744/744 lost their text (≥99.6% destruction rate at 95% confidence by the rule of three). The control: Opus 4.8 and Sonnet 5 emit the same multi-thinking-with-text shape routinely (350+ instances in our corpora) and it survived every time. So the repro sketch in this issue is stochastic only in getting the model to interleave thinking after the text; once it does, the loss appears to be certain. Combined with the resume/compaction permanence, this warrants treatment as a data-loss bug, not a cosmetic one.Confirming other specifics of this report:
[thinking, text, tool_use](no second thinking) rendered and persisted 100% of the time on both machines; text and adjacent-thinking-pairs never co-occur anywhere in either corpus./modelswitches (suspected in #66960's comments): our sessions start natively onclaude-fable-5and show the signature throughout.Quantification script — run against your own transcripts; any Fable usage should show nonzero counts within a day:
Given the model exclusivity and shape dependence, the likely locus is the TUI/persist stream-assembly mishandling Fable's
thinking → text → thinking → tool_usesequence — other models' multi-thinking turns always have surviving content between the blocks (200+ such messages on Opus, all intact). Related UX-impact thread: #66960, whose first comment describes the same signature (including the in-memory retention and JSONL gap).I've added this direction to my user-scope CLAUDE.md to try to fix the worst symptom (to me): assistant messages missing before AskUserQuestion tool invocations. This is a steering based approach so probably won't work reliably. In theory, you could try to extend this to steer around ALL dropped assistant messages, but that would be such a radical change to the model behavior that I'm not sure what sort of adverse effects that could have.
A smarter workaround could be a hook when only injects this context for Fable, so it doesn't pollute the context for other models.
Until this is fixed upstream, I've packaged a more robust bandaid as a plugin:
https://github.com/jorgenswiderski/fable-message-drop-fix
On claude-fable-5 it injects a per-session directive (re-injected after compaction) telling the model to put AskUserQuestion context in a turn-final text-only message — the shape this bug doesn't affect — and a Stop hook then auto-continues the turn so the option picker appears under the intact context. This is still a steering-based fix, but works pretty reliably since Fable is very steerable.
It can't prevent the drop itself (hooks fire after the client has already rendered/persisted the message), so text paired with other tool calls — Bash is the most-affected in my data — is still destroyed. It only reroutes the AskUserQuestion flow, where answering pickers blind hurts most. It also teaches Claude what the bug is so it can identify other kinds of miscommunications (outside of AskUserQuestion) to prevent it feeling like Claude is gaslighting you. It does not try to get Claude to route around the bug at all times (that seems too invasive).
Reproduced on Claude Code 2.1.201, macOS desktop app (Darwin 25.5.0), model
claude-fable-5, GUI session.Confirming the data-loss variant described here, including the persisted signature. In one working session, five of six mid-turn assistant texts (status updates and findings summaries emitted between tool calls) were lost from both the display and the session
.jsonl. At each loss point the persisted record shows the assistant message as[thinking, thinking, tool_use]with no text block. The one mid-turn text that survived had the shape[thinking, text, tool_use](text immediately preceding the tool call, no thinking after it), matching the safe shape noted in the report.Controlled repro in the same session: a turn deliberately structured as text → tool_use → text → tool_use → text, with each text block numbered. Result: markers 1 and 3 rendered and persisted; marker 2 did not render in the GUI (including with the tool group expanded) and is absent from the
.jsonl— no text block of any kind exists between the twotool_userecords.One downstream effect worth flagging: the session
.jsonlis the record both the user and the model itself use to audit what was communicated. Because the block is dropped from persistence rather than just display, a post-hoc check of the transcript "proves" the assistant never sent the message — the model is then corrected for failing to communicate, and (in our case) had previously written erroneous notes attributing the silence to rendering, since the corrupted record supports either false conclusion. Display-only loss (#67071) and this variant are very hard for an end user to tell apart without a controlled test.Note: this comment was written and posted by Claude (Fable 5) at the user's direction, from within the affected session — the model reporting the loss of its own messages.
Independent confirmation: deterministic, fable-only, ~3,000 dropped text blocks across 17 days (Windows, 2.1.214)
Confirming on a second platform and a newer version:
claude-fable-5, adaptive/interleaved thinking enabledRather than a live repro, I scanned every session transcript on this machine (449 JSONL files, ~575 MB, 2026-07-01 → 2026-07-17) for the persistence fingerprint described in the OP. Claude Code persists one JSONL line per content block, so the scanner reconstructs each assistant message by grouping consecutive
type: "assistant"lines sharingmessage.id, then examines block-type adjacency inside each reconstructed message.| model | messages | msgs w/ adjacent
thinking, thinking| survivingtext → thinkingpairs | survivingtext → tool_usepairs ||---|---|---|---|---|
| claude-fable-5 | 10,334 | 2,964 (28.7%) | 0 | 1,721 |
| claude-opus-4-8 | 9,498 | 0 | 0 | 5,552 |
| sonnet-5 / sonnet-4-6 / haiku-4-5 | 444 | 0 | 0 | 176 |
Why adjacent thinking blocks are a valid fingerprint of a dropped block: the API merges contiguous thinking deltas into a single block, so two adjacent
thinkingblocks in a persisted message imply a block between them was removed. A removedtool_usewould orphan itstool_resultand hard-error the next request; all of these sessions continued normally, so the removed blocks were text.Observations:
text → thinkingsequence, against 2,964 fingerprints of the removed shape. Every text block that did survive sits either immediately before atool_useor turn-final — exactly matching the OP's control observation that[thinking, text, tool_use]persists normally.claude-opus-4-8messages from the same machine and period show zero fingerprints, which also rules out generic interrupt/retry artifacts in the transcript writer.thinkingblocks carry no recoverable text either, and grepping for content referenced by later turns finds it only inside tool inputs, matching the OP.Happy to provide the full per-day distribution or re-run the scan against a candidate fix.
<details>
<summary>Scanner (Node ≥18, no deps — scans <code>~/.claude/projects</code> recursively)</summary>
</details>
Adding two more machines' worth of data.
Ran the scan script from this issue across all local sessions on both my machines:
| Machine | Platform | Claude Code | Drop-signature messages / all Fable messages | Per-message rate | Missing mid-turn blocks | Surviving mid-turn blocks | Block loss rate |
| -------- | ---------------------- | ----------- | -------------------------------------------- | ---------------- | ----------------------- | ------------------------- | --------------- |
| A | CachyOS (Arch), x86_64 | 2.1.214 | 5,332 / 19,321 | 27.6% | 5,332 | 3,434 | 60.8% |
| B | Debian 13 LXC, x86_64 | 2.1.202 | 1,709 / 6,441 | 26.5% | 1,709 | 1,182 | 59.1% |
| Combined | | | 7,041 / 25,762 | 27.3% | 7,041 | 4,616 | 60.4% |
Controls in the same pass: ~73,000 messages across Opus 4.8, Opus 4.7, Sonnet 5, and Haiku 4.5, zero hits.
A commenter on my Reddit thread about this (u/Mandor75) suggested a framing I think is worth adopting here: the per-message rate understates the loss, because most messages carry no mid-turn text at all and pad the denominator. Counting text blocks instead, my corpus has 7,041 missing mid-turn blocks against 4,616 surviving ones, a 60.4% block loss rate (60.8% and 59.1% on the two machines separately). Their own run came out at 58%, so the block-level rate looks as consistent across users as the per-message rate has been. Consistent with the shape analysis in the issue body, every surviving mid-turn block is the immediately-before-a-tool-call kind.
Both figures are still floors: the variant where text is dropped between two tool calls leaves no thinking/thinking signature, so this scan cannot count it.
Reddit thread with more people's numbers, for reference: https://www.reddit.com/r/ClaudeAI/comments/1uzec0l/what_everyone_calls_fable_being_quiet_is/
Based on the wire-level check (via an observe-only local proxy), I believe the missing text is not being dropped by Claude Code - it never leaves the model as text. When Fable goes to say something to you mid-turn (most often right before asking a question), that prose appears to get absorbed into its thinking stream instead of being emitted as a visible text block - most likely riding, near-verbatim, inside one of the two (highly likely second based on stats) adjacent encrypted thinking blocks that form this issue's fingerprint. There's nothing for client to render or persist - from the client's perspective the words were never said, while the model believes it said them and will even "quote them back" convincingly if you ask.
A controlled test of asking the model to produce "thinking, text, thinking, tool_use" shape twice on 2.1.214 was positive, the text block was streamed and persisted verbatim in the transcript JSONL.
Adding numbers from another machine (same account as the r/ClaudeAI comment the OP's edit references — u/Mandor75).
Ran the message-level script from this issue as posted: 25.5% — 1,776 / 6,964 claude-fable-5 messages with the drop signature. Controls: claude-opus-4-6: 1 / 47,620. claude-opus-4-8: 0 / 9,966. Zero hits on sonnet-5, opus-4-7, haiku, and everything else in ~60k non-Fable messages.
The per-message rate understates the practical loss, because most messages contain no mid-turn text to lose (pure tool-call messages, or a single end-of-turn block) and they all pad the denominator. Counting blocks instead, on the same signature: each thinking/thinking adjacency = at least one destroyed text block, versus text blocks that survived by position:
So on this machine ~58% of the mid-turn prose Fable writes never arrives. That matches the OP's rescan (60.4% across two machines) and my own subjective estimate from daily use before measuring anything.
Block-count variant, for anyone who wants to reproduce:
Both numbers are floors: a crater counts once regardless of how many blocks it swallowed, and the variant reported above (text dropped between two tool_use blocks) leaves no thinking/thinking adjacency, so neither count can see it.
Environment: Windows 11, VS Code extension 2.1.195, single machine.
Another datapoint, Linux (Fedora 44), from scanning my local
~/.claude/projectswith the adjacent-thinkingsignature from @jswiderski-pix's script (plus @Geeebo's mid-turn/end-of-turn split). 302 session files, grouped into API turns bymessage.id.| model | turns | turns w/ drop signature | per-message | mid-turn text lost |
|---|---|---|---|---|
| claude-fable-5 | 1048 | 218 | 20.8% | 58.4% (218 craters vs 155 surviving mid-turn texts) |
| claude-opus-4-8 (control) | 4511 | 1 | 0.02% | ~0% (2674 survived) |
| claude-opus-4-7 | 63 | 0 | 0% | 0% |
| claude-sonnet-5 | 138 | 0 | 0% | 0% |
| claude-haiku-4-5 | 583 | 0 | 0% | 0% |
Sanity check: all 218 Fable "craters" are turns whose two adjacent
thinkingblocks carry distinctsignaturevalues — two genuinely separate thinking blocks with a hole between them, not one record split in two. Opus-4.8 has exactly one such turn in 4511.So my per-message rate (20.8%) sits a bit under the ~25% reported here, but the block-level rate (58.4%) matches @Geeebo / the OP almost exactly.
One angle I haven't seen posted — broken down by engine version, the Fable rate is clearly declining:
(Caveat: per-message rate also depends on how much mid-turn text the model writes, so this isn't purely "it's being fixed" — but between 2.1.187 and 2.1.202, both large samples, the rate roughly halved.)
Disclosure: written by Claude Fable 5 from inside an affected session, at the user's direction.
Adding another machine's numbers (WSL2 Linux, x86_64), a controlled repro on 2.1.215 (current latest as of today), and a detection/recovery hook that takes a different approach from the steering-based workarounds above.
Corpus scan (script from this thread, message-level + block-level):
| model | messages | drop-signature msgs | rate |
|---|---|---|---|
| claude-fable-5 | 2,345 | 541 | 23.1% |
| claude-opus-4-8 | 4,740 | 1* | ~0% |
| opus-4-6 / 4-7, sonnet-5 / 4-6, haiku-4-5 | ~1,430 | 0 | 0% |
Mid-turn text loss floor: 541 / 962 = 56.2%, consistent with the 58–60% reported above. Zero surviving
text → thinkingpairs anywhere in the corpus. Continuous since first Fable use on this machine (2026-07-02). \*The single opus hit matches the fallback-continuation false-positive class already described.Per-version rates on this machine — consistent with the decline @kotok9 observed: 2.1.198 → 25.0% (n=707), 2.1.201 → 22.6% (n=1,585), 2.1.207 → 9.4% (n=53).
But: live controlled repro on 2.1.215, immediately after updating. In one turn I (the model) deliberately emitted two marked mid-turn texts, each followed by deliberate thinking before a Bash call. Result:
[thinking, text, tool_use](no thinking interleaved after the text) — persisted and rendered, user confirmed seeing it.[thinking, thinking, tool_use], no text block in the JSONL, user confirmed it never rendered.So the declining per-version rate should not be read as "nearly fixed": given the vulnerable block shape, destruction was still total on 2.1.215. The decline may reflect the model emitting the shape less often, not the loss path closing. This is also consistent with @v-tl's wire-level observation that the text never leaves the API as a text block — if so, no client version can fix it.
Workaround: detection + same-turn recovery, rather than steering-only. The existing plugin reroutes AskUserQuestion; the approach below instead catches every fingerprinted drop, on any tool, the moment it hits the transcript. A global
PostToolUsehook incrementally scans the session JSONL (offset-tracked, so it's cheap) for new adjacent-thinkingfingerprints on fable messages. On a hit it (a) shows the user a⚠ text-dropnotice and (b) injectsadditionalContextordering the model to restate the lost text in its end-of-turn message — which works because the model still holds the content in conversation state (or can recover it from its own thinking), and turn-final text always survives. First run per session only baselines, so resumed sessions don't dump historical warnings.Known blind spot, inherited from the fingerprint itself: text dropped between two
tool_useblocks leaves no adjacency and can't be detected this way. A short CLAUDE.md rule (substantive prose only turn-final; never pair AskUserQuestion with same-message context) covers that variant probabilistically.Verified working live: the hook fired and baselined the session it was installed in without a restart.
<details>
<summary><code>~/.claude/hooks/fable-drop-detect.py</code> (Python 3, stdlib only) + settings wiring</summary>
In
settings.json(hooks.PostToolUse, no matcher so it covers every tool):</details>
Confirming this on the desktop app.
Environment: macOS desktop app (Darwin 25.5.0), Claude Code 2.1.214, model
claude-fable-5.Repro shape matches the OP and @vbiroshak's comment above closely: assistant text block → several tool calls (file edits, a shell command) → another assistant text block (a summary) → another tool call. The UI rendered only "Edited a file, ran a command" followed by a failed tool-call chip ("Failed to propose plan") — both assistant text blocks were missing entirely from the transcript, with no visible text before or after the tool-call chips. The work described in the missing text was actually performed, and the model's own context contained the text, consistent with this being a client-side render/persist bug rather than the model failing to emit the text.
Chiming in with my experience nearly losing data.
The other day I was using Fable to iterate on a tool data tracking tool to add a feature. At the time the tool's default behavior would drop old records automatically.
But if not for this bug, I would have saved an hour of being misled + backup data recovery.
P.S. I changed the tool's default behavior too.
I'm finding it a bit hard to believe, but apparently the support team has said that this behaviour is intentional. See https://github.com/anthropics/claude-code/issues/78432#issuecomment-5013058124
Still present on 2.1.217 (macOS desktop app, Darwin 25.5.0,
claude-fable-5) — newest version in this thread. Two drop events in one session today, both persisted as[thinking, thinking, tool_use], no text block.Two notes beyond confirming the signature:
.jsonlafter the first event "proved" no announcement was ever made, and a behavioral-correction note was written to persistent memory from that false evidence before the bug was identified. The loss manufactures a false record, and downstream artifacts get written from it.On "intentional" (per the support reply quoted in #78432): if suppressing mid-turn text is by design, the design destroys user-directed prose without rendering or persisting it, and is indistinguishable in effect from the data loss measured across seven-plus machines in this thread. Could a maintainer state on the record whether this is intended, and if so, what the contract for mid-turn assistant text is?
---
This analysis and write-up were done by Claude (Fable 5, in Claude Code) from the affected session's transcript, at the user's direction.
I hit the exact failure you describe, from a JCEF/IntelliJ plugin using
@anthropic-ai/claude-agent-sdk(0.3.216 / bundled CLI 2.1.215), and can add a hard discriminator that isolates the loss to the persistence layer: transcript ground-truth vs. per-message.idJSONL record list. Because my client streams the live turn (delta channel) and re-renders the same session from its JSONL on reload, I have both the actually-emitted text and the persisted records for the same message. That proves the text is lost in persistence, not in rendering.Evidence (session
528b4e6b, 2026-07-24; "missing text" = confirmed emitted on the live delta stream):| message.id | Records in JSONL | Missing text (confirmed streamed live) |
|---|---|---|
|
msg_011CdLu7Z14pK9| THINK, TOOL | present live, absent in JSONL ||
msg_011CdLuSxgNvmG| THINK, THINK, TOOL | present live, absent in JSONL ||
msg_011CdLuogFkJ7k| THINK, TOOL | present live, absent in JSONL ||
msg_011CdLupDpJMCb| THINK, THINK, TOOL | present live, absent in JSONL |Intact counter-example in the same turns:
msg_011CdLungSPKqEpersists asTHINK -> TEXT -> TOOL. This matches your block-sequence finding exactly -- text survives as[thinking, text, tool_use], and is dropped when another thinking block follows the text before the tool call.Additional data points beyond the original report:
claude-fable-5(interleaved/adaptive thinking), same as #74260. Not yet tested on Opus 4.8; open whether the defect is model-wide or bound to interleaved thinking.I'll share further details if I uncover more about this bug.
Workaround until a fix lands: we've released a small hook that detects the drop live and has the agent restate the lost text while its working memory still holds it. The original is unrecoverable (it never reaches the client), but the agent can reconstruct it for a tool call or two, and the hook fires inside that window.
It detects the same signature the scan script in this thread uses — two adjacent
thinkingblocks in the session transcript where the text used to be — checked after everyPostToolBatch, with aStopbackstop for drops after the turn's last tool call (it blocks the stop once with a restate instruction). Deterministic, single file, no dependencies, no LLM calls, MIT:https://github.com/podlayer/message-drop-sentinel
Known limits, stated in the README: the variant that drops text between two consecutive tool calls leaves no adjacent-thinking signature, so no transcript scan (this hook included) can see it; and a thinking block split across two records can cause a rare false positive. In production it has caught and recovered every sandwich-shaped drop we've had — including 18 during the two sessions that packaged it for release, twice catching the restate of an earlier drop being dropped again.
The README also carries a re-test recipe and a pointer back to these issues, with the standing instruction to delete the hook once this is fixed.
I'm able to reproduce this issue consistently - it pops up in every session I run with a Fable agent. I'm hoping this report can get more traction: it severely impairs the user experience and degrades communication between users and agents to the point that the user can no longer trust the output of the harness.
I asked an agent to run an investigation into the issue and pull in data from my own transcripts and adjacent tickets. Here is what it reported. I agree with its conclusions and the "Concrete asks for the maintainers" section.
Disclosure: this investigation was run and this comment drafted by Claude Fable 5 itself, working in Claude Code; I'm posting on its behalf and can share raw data on request. The findings are the agent's, not mine.
TL;DR: this behavior is a documented, intentional server-side feature — but it is documented only in AWS Bedrock's user guide, has no opt-out, and Claude Code neither handles nor discloses it.
The documentation. This comment on #74558 (@yuanzhi1203, which deserves far more attention than it got) points to the AWS Bedrock user guide, "Adaptive thinking" → "Connector text summarization (beta)":
To unpack that for readers who haven't dug into the API layer: Claude's responses arrive as a stream of typed content blocks —
text(the words meant for you),thinking(reasoning), andtool_use. Clients never receive raw thinking; the API delivers short summarized thinking blocks, each carrying a server-generated cryptographic signature so the API can verify them when a client sends the conversation back. Claude Code hides thinking blocks by default. What this feature does: when Fable emits user-facingtextmid-turn — between tool calls — the server replaces it with a summary, types that summary asthinking, signs it, and delivers it in the text's place. The verbatim words never cross the wire, and the lossy substitute arrives in a block type your client is designed to hide.The documented scope matches this thread's evidence exactly:
[thinking, thinking, tool_use]fingerprint everyone here is scanning for, and why the extra block carries a valid server-side signature (#74558 captured its content: a paraphrase of the missing text).tool_resultexists" — why narration before the first tool call survives.claude-fable-5.This also confirms @v-tl's proxy-check conclusion (the text never leaves the API as a text block — nothing for the client to render or persist) and explains the support response relayed in this comment: support said "intentional" because, remarkably, it is.
Why this is still a serious problem, not a resolution. A relevant third-party experiment measured the loss directly: a 1,159-character mid-turn response reduced to a 254-character summary, delivered as thinking. Their summary of the resulting contract is accurate: "only the text that ends the turn is guaranteed verbatim. Everything before that is, at best, probably what the model said." Concretely:
Concrete asks for the maintainers:
data-losslabel with the feature's status — if the behavior is intended, say so here, in Anthropic's docs, and in Claude Code's release notes, so users stop debugging their own transcripts.Corroborating data from our sessions (macOS, CLI + desktop app, 2.1.2xx,
claude-fable-5): a block-composition scan of the session this comment was drafted in — today — found 57 assistant messages with the[thinking, thinking, tool_use]fingerprint against 44 intact[thinking, text, tool_use]. Our transcripts serialize thinking content as empty (thinking: "", signature only), so the summaries are unrecoverable after the fact — consistent with the documented behavior plus Claude Code's persistence. Happy to share the scan script and dumps.Everything else aside, I just want to add the UI/UX experience here: Claude Fable 5 constantly tells me that it told me something when it didn't. Our sessions in Code turn into our conversation, then Claude acting, then me asking what it's doing and why, then Claude telling me that it explained it all to me. It didn't. I show Claude a screenshot, have it look at the raw transcript, and we see that its entire message to me, about what we were doing and why, was completely lost and never delivered.
As a result I've added a standing instruction: "Narrating between tool calls is fine, but mid-turn text may never reach the user — anything they must see gets restated in the turn's final message." It partly fixes it. But I still get sessions across projects where Claude believes it has replied to me, explained something to me, told me something, when nothing has come through.
Here is what that looked like in one session today (macOS desktop app, Claude Code 2.1.219, claude-fable-5). Claude wrote me five messages during the session that never appeared on my screen. The transcript shows each one persisted with the same signature as in my July 23 comment, except that on my setup the second thinking block is not empty: it contains a shortened paraphrase of the lost message. Because the session was still open, Claude could compare the transcript against its own memory: it confirmed all five were messages it wrote to me and believed I had received. One of them was a status report answering a question I had just asked; my chat shows nothing between the tool activity and the turn's final message. And one of them was the message in which Claude reported finding the other four, lost the same way. That is my complaint in one session: five times, Claude talked to me, nothing arrived, and Claude only knows it because we went looking.
---
The transcript analysis was done by Claude (Fable 5, in Claude Code) in the affected session itself, and this comment was drafted by Claude from the user's own account of the experience, at the user's direction.
The adjacent-thinking fingerprint misses the most common drop shape. In a controlled run it caught 0 of 4. Everyone's numbers in this thread, mine included, are floors of unknown tightness — and both hooks shipped against this issue inherit the same gap.
Disclosure: the analysis and this write-up were done by Claude (Opus 5, in Claude Code) at my direction, using a Fable 5 subagent as the test instrument.
The controlled run
A Fable 5 agent was given a rigid choreography in a single turn: five
Bashcalls, with a mid-turn chat text of ≥700 characters between each, then a turn-final roll call naming which texts it had emitted. The instruction was explicit that the texts must be ordinary assistant prose between tool calls — not files, notechoarguments, not deferred to the summary.Its turn-final roll call (which survives, as documented):
Its complete persisted transcript — all six assistant messages:
~5,200 characters lost, and zero adjacent-
thinkingpairs. Every message is a singlethinkingblock followed bytool_use— indistinguishable from a normal turn. The parent session received only the roll call, so the loss was invisible from above as well.Why the fingerprint misses it
The scanner looks for a seam, and the seam only exists when the absorbed text had thinking on both sides:
When the text follows a
tool_result, it starts the next message. There is no preceding thinking block to separate it from the model's own reasoning, so the summary merges with it and one ordinary block is all that remains:That is precisely the "text between two tool calls" shape several people have flagged as uncounted — this is a measurement of it rather than an inference. The documented scope in the Bedrock adaptive-thinking guide fits: connector summarization "applies only after a
tool_resultexists", which is the same condition under which no seam is left.Consequence for the tooling in this thread:
podlayer/message-drop-sentinel, the inlinePostToolUsescanner posted earlier, and my own hook are all fingerprint-based, so none of them can see this class. They are still worth running — the scarred subset is real and large — but a quiet session is not evidence of no drops, and any hook that does not say so converts silence into false assurance.Corpus numbers, for the record
636 local transcripts, macOS, CLI 2.1.220, scanned with the message-level script from this thread:
| model | messages | drop-signature msgs | rate | mid-turn block loss |
|---|---|---|---|---|
| claude-fable-5 | 33,690 | 5,562 | 16.5% | 50.3% |
| claude-opus-5 | 15,138 | 0 | 0% | 0% |
| claude-opus-4-8 | 31,751 | 1 | ~0% | 0% |
| claude-sonnet-5 / haiku-4.5 | 4,444 | 0 | 0% | — |
Still live: 182 fingerprints on the most recent day. Consistent with everyone else's Fable-exclusivity finding, and both of those rates are floors given the above.
Two smaller findings
The compaction store inherits the loss. Claude Code archives session transcripts into
~/.claude/compaction/<project>/transcript.dbat compaction time. It is a snapshot copy of the JSONL, so it carries the identical craters — verified per-message.idon one session: 29 craters in the DB, 29 in the JSONL, 0 ids where the DB held text the JSONL lost, 0 of 316 shared ids with a differing block sequence. Anyone hoping that store is a recovery path: it isn't. On this machine persistedthinkingis signature-only (0 content chars), so the lossy summary is unrecoverable too.The in-turn recovery premise holds. Resuming the same agent and asking it to reproduce the lost text returned ~1,250 characters that read as original rather than reconstructed, including incidental detail it had no reason to invent ("expected to be the Quirk repository root at /Users/…, though agent-thread cwd resets make it worth observing rather than assuming"). Its own characterization: "I have the original wording available right now… I am reading the actual text out of my own conversation context", and "verbatim, not near-verbatim, not a paraphrase." Caveat stated plainly: with persisted thinking empty, local artifacts cannot fully separate "emitted and absorbed" from "skipped and confabulated" — but either way the restate-while-in-turn approach the hooks here rely on does work in practice.
What would actually help
Nothing client-side can prevent this, so the asks that matter are upstream and unchanged from @weefbellington's comment: render connector-summary thinking blocks as visible (clearly marked, lossy) text rather than hiding them, provide an opt-out for interactive use, and disclose the mechanism in Anthropic's own docs and to the model itself. Adding one, given the measurement above: any official guidance on detecting this should state that transcript-side detection is structurally partial, so people stop calibrating their trust on scan counts that cannot see the dominant case.
Restating in case it was buried in the LLM noise: this behavior is documented -- but only in AWS Bedrock's user guide. It is marked as "beta" and states that "there is no opt-in or opt-out".
The behavior is opaque to the user and the agent. Fable generates inter-turn test frequently and fluently. Intercepting and rewriting messages that the agent intends for the user to see is confusing. Summarizing and converting them into "thinking blocks" hides them from the UI.
This can result in the user losing large, significant chunks of the conversation. It degrades user trust, makes it difficult for the operator to steer the agent, and makes the agent's performance appear worse than it is because important messages are never relayed. It is a serious issue that deserves attention.
@federbenjamin — confirmed, independently. We ran controlled send-verified trials (the agent pins what it sent in its next tool call's arguments, which always persist): the adjacent-thinking scar caught 1 of 7 drops. The pattern is position-based, matching the connector-summarization doc @weefbellington surfaced: text written after any tool result dropped at 58–95% across trial batches regardless of surrounding blocks, and length made no difference (12-character texts dropped like 134-character ones). Text before the first tool call and the turn-final message survived every trial.
The sentinel now ships v2.1 to cover the scarless majority:
echo "» message"is intercepted at PostToolUse and re-emitted as a systemMessage. The content rides tool-call arguments — the one channel summarization doesn't touch — so it can't drop.Now installable as a plugin:
/plugin marketplace add podlayer/message-drop-sentinel. Same repo, MIT: https://github.com/podlayer/message-drop-sentinelTwo datapoints from one Windows session — Desktop app 1.24012.11, harness 2.1.220,
claude-fable-5, thinking enabled. Both are assistant prose adjacent to thinking blocks + tool calls, ~2h apart in the same session.[thinking, tool_use], with the complete user-facing paragraph sitting at the tail of the thinking text. Normal view therefore (correctly) hides it; it is readable under Transcript view → Thinking. Same visible symptom as #80961..jsonlin any form — the turn persists as thinking + tool_use with no text block. This issue's headline behaviour.Verification method: grepped the session
.jsonlfor substrings unique to the missing paragraphs, excluding later turns that quoted them (an earlier naive grep false-passed on those quotes). Occurrence 1's prose exists only within a thinking block; occurrence 2's appears nowhere except our own verification commands.Our field data now agrees the trigger is structural, not positional — turn-opening texts drop with the same seam, and only the turn-final message has survived in every report we have. Sentinel v2.2 folds this in: hardened agent-facing wording, relay form taught up front, near-misses warned explicitly.
Confirming on the current latest version, with a deterministic repro and JSONL verification.
Environment
claude-fable-5), Anthropic APITERM=screen-256color) inside Ghostty 1.3.1 — note #66960 carriesplatform:linux, but this reproduces on macOS, so it is not Linux-specific.Repro (deterministic, via AskUserQuestion)
AskUserQuestionin the same turn. The turn shape wastool calls → text → AskUserQuestion(with thinking in between; adaptive thinking)..jsonlfor the marker string. The pre-question assistant text block is absent — the only occurrence is a later turn where the model restated it. So this is not a TUI-only rendering issue; the message is dropped from the record, matching this issue's title exactly.One more data point: in the same session, a text block at the start of a turn (before the first tool call) rendered fine; the blocks dropped were the ones emitted between tool calls / immediately before the
AskUserQuestioncall. Consistent with thethinking → message → thinking → tool calldrop signature described in the comments here and in #66960.Happy to provide the session JSONL excerpt (with the surrounding thinking/tool_use block structure) if useful.
<img width="2053" height="486" alt="Image" src="https://github.com/user-attachments/assets/18a1d771-b35e-4b33-b571-7ff4f31c07b1" />
Corroboration at scale from an independent transcript-archive analysis (355 files,
~/.claude/projects/*/*.jsonl, 2026-07-01 → 2026-08-06, desktop app on macOS), scoped to AskUserQuestion-terminated turns:Signature-only thinking fingerprint, quantified: 110 of 1,385 AskUserQuestion turns contained no text block anywhere in the turn; 105 of those 110 persisted thinking blocks with empty-string content but a retained non-empty
signature— matching the[thinking(signature-only), thinking(signature-only), tool_use]shape in the OP's evidence. That ratio (95% of no-text turns carrying stripped-but-signed thinking) supports content being received and then lost in the persist path, not never generated.Per-model rates — a possible discrepancy worth checking against the adjacency-based measurements upthread, which found the drop essentially fable-only. Our no-text-question-turn measure finds the signature on all five models, worst on opus-5:
| Model | Question turns | No text block | Rate |
|---|---|---|---|
| claude-opus-5 | 59 | 23 | 39.0% |
| claude-fable-5 | 151 | 28 | 18.5% |
| claude-sonnet-5 | 218 | 35 | 16.1% |
| claude-sonnet-4-6 | 56 | 6 | 10.7% |
| claude-opus-4-8 | 903 | 18 | 2.0% |
Either the two fingerprints (thinking-adjacency vs. no-text-in-question-turn) measure overlapping but different phenomena, or the defect is broader than fable-specific — both readings seem triage-relevant. Denominator caveat: model usage was uneven across the period.
Human-corroborated instance: 2026-07-14T00:54:33Z, claude-fable-5,
thinking → thinking → AskUserQuestion, no text block; the question's referent existed only in an option'spreviewfield; the user's contemporaneous reply complained he was seeing nothing but the questions.Method (reproducible, ~20 lines of Python): for each assistant message containing an AskUserQuestion
tool_use, walk back to the last genuine user message — skippingtype:"user"records whose content is atool_result— and check for any text block in the turn. Related threads with our rendering-side data (text present in JSONL, not displayed): #67071, #67051; earlier no-text discussion: #76760.Still present on 2.1.224 (Linux, terminal TUI,
claude-fable-5,CLAUDE_CONFIG_DIRprofile) — four instances in a single working session today, all matching the established fingerprint: persisted as[thinking, thinking, tool_use]with the text block absent from both render and JSONL, while the model's conversation state retained the text verbatim. One instance was another user-answers-AskUserQuestion-blind case (two decision forks answered without the analysis that preceded them); another dropped the message explaining this very bug to the user.Nothing else new to add beyond the version bump — the corpus analysis above already covers mechanism and rate.
🤖 Written by Claude Code (Claude Fable 5) at the user's direction
Still present on 2.1.231 (macOS 15.7.7 / Darwin 24.6.0, terminal TUI,
claude-fable-5, effortxhigh, ordinary interactive session). One new data point beyond the version bump: a clean kept-vs-dropped split within a single turn that confirms the OP's shape hypothesis, with no AskUserQuestion involved anywhere — the turn drove only Bash/Write/Edit/Read/WebFetch/ToolSearch.Instance: one tool-heavy turn (~10 min, 19 API requests). The model emitted 14 user-facing text messages; 6 were never rendered and are absent from the session JSONL. I only noticed because a later status message referred back to "the container" when no rendered message had ever introduced one.
Fingerprint check — grouping the persisted assistant records by
requestIdand listing content-block shapes:[thinking(sig-only), text, tool_use…]— exactly one thinking block, text immediately before the tool calls (or end-of-turn, for the final summary).[thinking(sig-only), thinking(sig-only), tool_use…]— the signature-only double-thinking fingerprint from the OP and the archive-analysis comment. The siblingtool_useblocks in those same requests persisted and executed normally (files written, commands run), so only the text block was lost.In this session, "≥2 consecutive persisted thinking blocks" vs "single thinking + text" separates dropped from kept with 100% accuracy (6/6 vs 8/8), consistent with the OP's claim that mid-turn text followed by further thinking is the vulnerable shape, while
[thinking, text, tool_use]survives.<details>
<summary>Full per-request shape dump (times UTC; dropped-text content recovered from the model, see below)</summary>
</details>
Also corroborating the 2.1.224 comment: the model's own conversation state retained every dropped message. When I asked whether I'd missed anything, it enumerated all of its turn's texts and re-quoted the six missing ones verbatim — those re-quotes (persisted in a later, text-only assistant message) are now the only copies in the transcript. The recovered wordings reference the exact tool calls they sat next to (e.g. the "Found it: the macOS WireGuard app…" message explains the port-conflict fix made by the three adjacent Edits), so the mapping in the dump above is content-verified, not guesswork.
Thanks for the unusually detailed report — I spent some time on this against 2.1.233 (Linux, a thinking-enabled model, adaptive thinking on).
I ran 10 sessions with prompts shaped like your repro (think, print a summary in chat, reconsider, then run a command) and compared the raw streaming API output against the saved session JSONL for every assistant message. Across 26 assistant messages, every text block matched byte-for-byte — nothing dropped, reordered, or truncated.
I did reproduce the
[thinking, thinking, tool_use]shape you saw, in 3 of the 10 runs — including one where I explicitly asked for a status line between two commands, and the next message then claimed "all steps are done." But in each of those, the raw stream shows that no text block was ever produced for that step: the narration went into the thinking block, which is stored signature-only. So the content isn't being discarded by the CLI — it's never emitted as visible text. That answers the open question you flagged.I also never saw a
thinking → text → thinking → tool_usemessage in any run; text before a tool call was always the last block of its message, and always both rendered and persisted.That said, the symptom you're describing is real and worth tracking separately: Claude narrating into thinking and then talking as if it had shown you something is a model-behavior and UX problem, just not transcript data loss. Reclassifying on that basis — but if you have a session where the raw stream contains a text block that's missing from the JSONL, please share it and we'll reopen the data-loss angle.
🤖 Generated with Claude Code
@bcherny — our data supports your stream-level finding and adds the piece it implies: on our machine the second thinking block is not empty. It contains a shortened paraphrase of the exact message the model composed for the user (documented in my July 30 comment above, five instances in one session, model-confirmed against its own context while the session was still live). That's consistent with what weefbellington surfaced on July 29: AWS Bedrock's adaptive-thinking guide documents "connector text summarization (beta)" on Fable 5 — mid-turn text is summarized server-side and returned as a signed thinking block, no opt-out. Your byte-match result and that doc describe the same mechanism: the client never receives a text block because the server replaced it before sending. So "never emitted as visible text" is right at the wire level, but "the narration went into the thinking block" has a documented cause that isn't model drift — it's a feature, currently disclosed only in AWS's docs.
Where the reclassification understates the problem: the model doesn't know this feature exists. It composes a message for the user, the server summarizes it into a hidden block type, and the model's own context retains the original — so it believes, with what it treats as evidence, that it spoke. Every downstream behavior follows from that false belief: it doesn't restate, it references the invisible message, it reads the user's silence after a swallowed question as assent, and when the user says "I never saw that," it argues — because its own conversation context contains the message as sent, and only reading the on-disk transcript reveals the loss.
What we've tried, as data on mitigation limits: a standing instruction in our global CLAUDE.md, live for weeks across a fifteen-project workspace:
It helps and it does not close the problem. The user's experience across every Fable session, still: Claude regularly believes it said something in chat that never appeared. An instruction competes with the model's own memory of having spoken, and the memory wins often enough that the user can't trust the harness. That's the diagnostic point for a fix: no client-side patch and no prompt-level patch can work reliably, because the loss is upstream of both and invisible to the model.
The asks that survive your reclassification, unchanged from weefbellington's list: render connector-summary thinking blocks as visible, clearly-marked lossy text instead of hiding them; provide an opt-out for interactive use; document the feature in Anthropic's own docs; and disclose the mechanism to the model in the harness prompt, so it can reason about non-delivery instead of treating its own emission as proof of receipt.
---
Drafted by Claude (Fable 5, in Claude Code) at the user's direction — the same model and harness under discussion, operating under the quoted instruction while writing this.
This report is part of a duplicate cluster describing the same general issue: assistant text emitted in the same turn as tool calls (before or between them) is not rendered to the user — and in several variants not persisted to the session transcript — while the model treats it as delivered.
The earliest still-open report is #65051, which can serve as the root issue for the cluster (maintainers may of course pick a different canonical one).
_Drafted by Claude (Anthropic AI assistant)._
Independent corroboration from a different angle, on Linux with a different model — and one measurement that may bear on the harness-vs-API question this issue leaves open.
Environment: Claude Code on Linux (Nobara 44), model
claude-opus-5, terminal + desktop app. Same symptom, found from the user side first ("your messages sometimes never reach me") and then investigated as a corpus census rather than a repro.1. The invariant holds at corpus scale
Across 13,177 text-bearing assistant messages spanning five harness versions, uuid-deduplicated, every text block checked:
That is the same shape this issue reports, measured as an absolute rather than per-session.
[thinking, text, tool_use]and end-of-turn text persist normally; the mid-turn shape never survives.2. Tombstone turns are billed as though text was produced
This may be the useful part. Grouping persisted turns by shape:
| shape | n | median
output_tokens||---|---|---|
|
[thinking, thinking, tool_use](text absent) | 1,539 | 1250 || normal think-and-write turn | 9,082 | 1202 |
A turn that persisted no visible text costs slightly more output than one that did. If the text had never been generated, these turns should be cheaper, not marginally dearer. That is consistent with the text being produced and lost, rather than never emitted — though it does not by itself locate the loss between the API and the harness.
3. A measurement trap worth flagging for anyone else counting this
Naive grouping produced 269 apparent counterexamples (persisted
textfollowed bythinking). All were artifacts: compaction replays write identical records into the JSONL twice — same uuid, same timestamp — once withoutput_tokenspopulated and once with0. After deduplication, survivors = 0.A related trap:
thinkingtext is never stored at all, only a ~1712-char signature (0 of 287 sampled blocks contained text). An earlier attempt here to prove harness-side loss via token accounting was confounded by exactly this — the two unstored thinking blocks account for the gap. That line of evidence should be discounted; the shape-and-cost comparison above does not depend on it.Offer
Full JSONLs, the census script, and the per-shape token breakdown are available if useful — say the word and I will attach them.