[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

Status Open
Reported on v2.1.210
Maintainer reply None cached
Activity 4 comments · opened Jul 15, 2026

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.

View original on GitHub ↗

4 Comments

sipak · 1 month ago

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) on claude-fable-5 across macOS + Ubuntu, CLI 2.1.69 and 2.1.211, two accounts, hooks on/off: text emitted before tool_use in 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_URL shows that at affected positions the SSE stream contains no text block at all — the content arrives as content_block_start type=thinking with a single signature_delta and no thinking_delta (genuine encrypted thinking carries an empty thinking_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 to display:"summarized":

  • the long text arrived as a genuine text block (first survival in 13 runs), and
  • residual misroutes streamed as readable summarized thinking instead of ciphertext (paraphrased, not verbatim — but no longer silent loss).

Note showThinkingSummaries: true in settings does not change the outgoing display value (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:

  • The model's own context is intact (signatures round-trip and decrypt server-side): asking Fable to quote its previous message verbatim recovers the lost text — useful triage confirmation that generation succeeded and only delivery-as-text failed.
  • Under 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.

shohu · 1 month ago

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 thinking records followed by the tool_use record, with the user-facing text block absent entirely. Unaffected responses in the same session show the normal thinking, text, tool_use sequence. 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).

  • Affected: thinking, thinking, tool_use (text missing)
  • Unaffected: thinking, text, tool_use

2. 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), and AskUserQuestion tool calls alike. The turn-final message (no trailing tool_use) was always rendered and persisted.

Happy to provide sanitized JSONL excerpts if useful.

sipak · 1 month ago

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_result exists 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_result yet) and turn-final text always surviving. Two checks I ran against it:

  • Scope holds at scale. Re-scanned my whole local corpus (~900 sessions): 1263 swallowed-text envelopes, 100% on Fable, and 1263/1263 occurred after a 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.)
  • The content is recoverable from the model. I forced a 200-item numbered noun list into a swallowed block (thinking, sig 4232 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' "the signature field still carries the encrypted full thinking" under display: "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 under display: "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:

  1. Classification. The spec's model of connector text is disposable narration ("Let me check that file next…"). In Claude Code the model also answers between tool calls: replies to mid-turn interjections, the preamble before an AskUserQuestion widget (#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.
  2. Client rendering (fixable here, today). Claude Code sends 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. Honoring showThinkingSummaries (#77460) or labelling these blocks as summarized narration would convert silent loss into visible, if lossy, content.
  3. Documentation. platform.claude.com/docs/en/build-with-claude/thinking has 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.