[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 5 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 · 3 months 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 · 3 months 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 · 2 months 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.

Showing cached comments. Read the full discussion on GitHub ↗