[Bug] Fable mid-turn messages not visible to operator, long mid-turn assistant text is emitted as a thinking block instead of a text block
Bug Description
Fable mid-turn messages are consistently invisible to the operator
Environment Info
- Platform: darwin
- Terminal: tmux
- Version: 2.1.210
- Feedback ID: 5b5c1689-276a-48ba-a6f5-f76b11e2715c
Errors
[]
---
Model: claude-fable-5 · Claude Code: 2.1.210 · Platform: macOS arm64 (Darwin 25.5.0)
Summary: With interleaved thinking + tool use, Fable 5 systematically fails to deliver substantive prose written mid-turn (i.e., followed by more tool calls in the same response). The content arrives at the client as an extra signed thinking block rather than a text block, so Claude Code never renders it as a message and strips it to an empty stub in the session transcript. The model believes it answered; the user sees nothing. Opus 4.8 in the identical setup does not do this.
Evidence (session JSONL analysis, one representative session of 124 API responses):
- Only 19 responses contained text blocks. Every mid-turn survivor was ≤157 chars (short transitional one-liners). The only long survivor (~3KB) was the turn-final message with no trailing tool call.
- 31 responses show the failure fingerprint: [thinking, thinking, tool_use] under one message id, where the second thinking block (empty content + valid signature after persist) sits exactly where the emitted prose should be. Grep for distinctive phrases from the lost messages returns zero hits.
- Lost content included direct answers to mid-turn user questions and a multi-paragraph plan the user explicitly asked to have restated — it was "restated" into a thinking block again.
- Control: Opus 4.8 transcripts on the same machine persist mid-turn text blocks of 1–3KB alongside tool_use without issue.
Impact: the model appears unresponsive/rude — it "answers" mid-turn questions invisibly and keeps working. Recurs in every Fable session observed, never with Opus.
Repro: ask Fable 5 a question mid-turn while it's in a long tool-use loop, such that its answer exceeds a few hundred characters and is followed by further tool calls. Then check the session JSONL for [thinking, thinking, tool_use] groups and grep for the answer text.
Workaround observed to help: keeping mid-turn narration to one short sentence; placing anything user-critical in the turn's final message.
4 Comments
Independent confirmation with raw-wire evidence, plus a request-side finding that materially changes where the fix lives. Full evidence bundle (SSE captures, timing data, repro prompt, proxies, session JSONL): https://gist.github.com/sipak/b30ccbda55a8570fecc17ad1fdd70e98
Scale of testing: ~20 headless suites (
claude -p, 6 message shapes) onclaude-fable-5across macOS + Ubuntu, CLI 2.1.69 and 2.1.211, two accounts, hooks on/off: text emitted beforetool_usein the same message is lost — 12/12 for ≥1.5k-char texts, ~40–60% for short ones. Same prompts on Opus 4.8 (5 suites) and Haiku 4.5: zero losses. Turn-final text never lost. Matches this issue's length observation exactly.It is server-side. A logging proxy on
ANTHROPIC_BASE_URLshows that at affected positions the SSE stream contains no text block at all — the content arrives ascontent_block_start type=thinkingwith a singlesignature_deltaand nothinking_delta(genuine encrypted thinking carries an emptythinking_delta+ signature — a structural fingerprint distinguishing the two). Signature length scales with the lost content (~2k chars → sig ≈ 3000 vs ~400 for normal short thinks). Per-chunk timestamps add a temporal fingerprint: the wrapped envelope opens and closes in 0–90 ms while the preceding thinking block stays open ~8 s — i.e. the text is generated normally, then sealed post-hoc. This is why the UI shows "Thought for 0s" where a paragraph should be, why the JSONL stores an extra empty signed thinking block, and why no client version can fix the rendering.The request-side lever: Claude Code sends
thinking: {"type":"adaptive","display":"omitted"}for Fable. Rewriting it in-flight todisplay:"summarized":Note
showThinkingSummaries: truein settings does not change the outgoingdisplayvalue (that's #77460) — honoring it would turn this from silent data loss into visible summaries today, while the misrouting itself gets fixed server-side.Two more observations for whoever picks this up:
summarized, Fable's next-turn thinking shows it believes it failed to emit the texts ("I realize I missed including the required markers…") — the misclassification appears to sit between generation and stream assembly.Related: #75034 (same double-thinking signature-only fingerprint, Fable-only attribution), #74176, #74558, #67761, #75916, #77849, #76210, #77960, #75900, #75217; third-party manifestation of the same wire format: anomalyco/opencode#31738. Pre-Fable ancestors closed "not planned": #21751, #24691, #24733, #50597.
Still reproducible on v2.1.212 with
claude-fable-5(CLI on macOS, subscription auth). Two observations from inspecting the session JSONL that may help narrow this down:1. Transcript fingerprint: two consecutive empty thinking records — the text never reaches the transcript.
Each affected response is persisted as two consecutive
thinkingrecords followed by thetool_userecord, with the user-facingtextblock absent entirely. Unaffected responses in the same session show the normalthinking,text,tool_usesequence. So this is not only a rendering issue: the text block is missing from the session JSONL as well (matches what others reported about Ctrl+O / persistence).thinking,thinking,tool_use(text missing)thinking,text,tool_use2. Non-deterministic within a single turn, no strict length correlation.
In one turn with 7 tool-calling responses, 4 mid-turn texts were dropped and 3 survived. A ~60-char text was dropped while texts of similar length (46–68 chars) survived; a ~2,000-char report was also dropped. Drops occurred before
Bash,Write(file), andAskUserQuestiontool calls alike. The turn-final message (no trailingtool_use) was always rendered and persisted.Happy to provide sanitized JSONL excerpts if useful.
https://github.com/anthropics/claude-code/issues/74558#issuecomment-5073554965
Correction to my earlier comment, and what I think the actionable ask is.
@yuanzhi1203 identified this in #74558: the behavior is documented — AWS Bedrock, Adaptive thinking § Connector text summarization (beta). On Fable 5, text emitted between tool calls is summarized server-side and returned as a thinking block instead of a text block; "final assistant answers (after all tool use is complete) … remain plain text"; summarization "applies only after a
tool_resultexists in the conversation"; "short text segments may pass through"; and "there is no customer opt-in or opt-out."That spec retro-predicts every measurement I posted, including the two I couldn't explain: the first response of a turn surviving (no
tool_resultyet) and turn-final text always surviving. Two checks I ran against it:tool_result— zero exceptions to the documented scope. (+504 in the 8 days since the first scan, i.e. ~500 assistant messages a single user never saw.)thinking,sig4232 chars, no text block emitted), then asked for it in the turn-final message: all 200 items came back, in order, no duplicates. Signature length tracks the original (~2.7k chars → 4232; a normal short think ≈ 400), consistent with the docs' "thesignaturefield still carries the encrypted full thinking" underdisplay: "omitted". So the loss is client-visible only — asking Claude to restate a suspected-lost message in its final answer works. Only the human-visible rendering underdisplay: "summarized"is lossy (cf. this independent experiment: a 1159-char passage surfaced as a 254-char synopsis).So my "no client version can fix this" framing was wrong, and this issue cluster is presumably triageable as working-as-designed — which would explain the silence across ~10 reports. What remains genuinely broken, though, is worth separating:
AskUserQuestionwidget (#74176 — user sees a bare question), analyses before a follow-up call. Those are the answer, not connective tissue, and they are the ones being deleted — with length being the strongest predictor (≥1.5k chars: lost ~always in my runs), i.e. exactly the substantive ones.thinking: {"type":"adaptive","display":"omitted"}, which turns "summarized" into "invisible", and renders the block as "Thought for 0s" — indistinguishable from a real short think, with no signal that content was withheld. HonoringshowThinkingSummaries(#77460) or labelling these blocks as summarized narration would convert silent loss into visible, if lossy, content.platform.claude.com/docs/en/build-with-claude/thinkinghas zero occurrences of "connector"; the only description is on another vendor's docs site. It cost me two days of wire-level reverse engineering to rediscover.Raw SSE captures, timing data, repro prompt and proxies remain in the gist linked above, for anyone wanting to reproduce the wire shape.