[BUG] Fable 5 thinking blocks come back empty ("thinking":"") in VS Code extension 2.1.233 — Opus 5 unaffected, worked on 2.1.228
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (VS Code extension 2.1.233)
What's Wrong?
Since the VS Code extension updated to 2.1.233, thinking blocks returned for claude-fable-5 arrive with an empty thinking string (signature only). The UI shows "Thought for Ns" with a chevron, but expanding it shows nothing — there is no thinking text to display. Focus view is off. showThinkingSummaries: true is set in ~/.claude/settings.json.
The same client with claude-opus-5 returns thinking text normally, so this is model-specific (Fable 5) and version-specific (2.1.233).
Evidence from the session transcripts in ~/.claude/projects/<project>/*.jsonl:
- Every thinking block written by client
2.1.233forclaude-fable-5looks like:
{"type":"thinking","thinking":"","signature":"CAIS..."}
- One session spans the update and shows the flip inside a single conversation:
- lines with
"version":"2.1.228", modelclaude-fable-5→ thinking text present - lines with
"version":"2.1.233", modelclaude-fable-5(from 2026-08-15T06:43Z onward) →"thinking":""on every block - Across the last ~25 sessions: ~1,900 Fable 5 thinking blocks on 2.1.228 had text; 0 of ~90 on 2.1.233 do.
- Opus 5 sessions on 2.1.233: thinking text present.
Nothing in the changelog for 2.1.229–2.1.233 mentions a change to thinking display.
What Should Happen?
Fable 5 thinking summaries should be shown (as they were on 2.1.228 with the same settings), or if summaries are intentionally unavailable for this model, the UI should say so instead of showing an expandable "Thought for Ns" that is empty.
Error Messages/Logs
# transcript line shape (2.1.233, claude-fable-5)
{"type":"thinking","thinking":"","signature":"CAIS..."}
No error output; the request succeeds.
Steps to Reproduce
- VS Code + Claude Code extension 2.1.233 (macOS, darwin-x64).
~/.claude/settings.jsoncontains"showThinkingSummaries": true, model set toclaude-fable-5(also reproduces withclaude-fable-5[1m]). - Make sure Focus view is off and Extended thinking is on in the
/menu. - Ask anything that triggers thinking (e.g. "think for a moment").
- Observe "Thought for Ns" with a chevron; expanding shows no text.
- Open the session's
.jsonlunder~/.claude/projects/...— everythinkingblock has"thinking": "". - Switch model to Opus 5 (
/model) and repeat — thinking text is present.
Claude Model
Other — claude-fable-5 (also claude-fable-5[1m]); Opus 5 unaffected
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.228 (VS Code extension) — Fable 5 thinking text was present up to the moment the extension updated to 2.1.233 mid-session.
Current Version
VS Code extension 2.1.233 (anthropic.claude-code-2.1.233-darwin-x64); standalone CLI on the machine is 2.1.218 (not used for these sessions).
Environment
- OS: macOS (Darwin 25.5.0)
- Client: Claude Code VS Code extension 2.1.233
- Auth: Claude subscription (not API key)
- Effort level: xhigh
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Root-caused this on our side (CLI 2.1.233, macOS,
claude-fable-5,showThinkingSummaries: true; same symptom as above and as in my duplicate #86913: 366/366 Fable thinking blocks empty today, Opus 5 fine, the 2.1.227 binary fine).The empty summaries are triggered by the request header
x-cc-atis: <16-hex>that 2.1.233 sends on first-party requests. Everything else in the request is irrelevant. Method: a local logging/rewriting forwarder onANTHROPIC_BASE_URL(with_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL=1so the client keeps all first-party behaviour), same prompt,--thinking-display summarized, then removing one request feature at a time:| request variant (2.1.233, Fable 5,
thinking: {type:"adaptive", display:"summarized"}on the wire) | thinking_tokens | thinking block on the wire ||---|---:|---|
| unmodified (first-party) | 404 | empty |
| minus
advanced-tool-use-2025-11-20beta + deferred/eager tool flags +DeferredToolPlaceholder| 781 | empty || minus
cache-diagnosis-2026-04-07beta +diagnosticsbody field | 440 | empty || minus the extra first-party system blocks | 437 | empty |
| minus
x-client-request-idheader | 508 | empty || minus
x-cc-atisheader | 1170 | summary text (900 chars) || minus
x-cc-atisheader (repeat) | 1290 | summary text (1032 chars) |Interactive sessions behave the same: a normal
claudestart through the plain forwarder (which, being non-first-party, never sendsx-cc-atis) shows summaries in the TUI again; a normal direct start does not.Where the header comes from: the value is not computed client-side, it is echoed from server-delivered client data cached in
~/.claude.jsonunderclientDataCacheSlots.bi1-<hash>.data— next toexperimentKey: "claude_code_ribbon_boulevard_experiment",cedar_lagoon: {"claude-fable": true, "claude-mythos": true}andcedar_basin: "2026-08-31". On this machine that slot was first written 2026-08-15T09:55Z (start of the first affected session). The 2.1.227 binary on the same machine/account sends nox-cc-atisand gets summaries; a static diff of the 2.1.227/2.1.228/2.1.233 bundles shows theshowThinkingSummaries→thinking.displayrequest path is identical (the header code itself also already exists in 2.1.227 — it just has no assignment to send). So this looks like a server-side treatment keyed on that experiment assignment that drops Fable 5 summaries even when the request asks fordisplay: "summarized"; Opus 5 requests carrying the same header are unaffected.Two side notes for anyone reproducing:
-p/headless mode forcesdisplay: "omitted"unless you pass--thinking-display summarized, so headless tests look empty for every model — test interactively or with that flag.--thinking-display summarizedflag andshowThinkingSummariesare not a workaround here; the request already carriesdisplay: "summarized".Workarounds until the treatment is fixed: run the 2.1.227 binary (
~/.local/share/claude/versions/2.1.227), or put a local forwarder onANTHROPIC_BASE_URLthat deletesx-cc-atis(with_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL=1remote control / tool search keep working). Happy to share the forwarder script or raw captures.Follow-up to my comment above: the
x-cc-atistreatment is not only a display issue — it also reduces how much Fable 5 thinks. Measured withusage.output_tokens_details.thinking_tokens(which 2.1.233 records per response; it counts internal thinking, not the summary text — we saw 19k thinking tokens on a response whose summary block was empty).18 fresh paired probes tonight (
claude -p --model claude-fable-5 --effort xhigh, clean env, same prompt per pair; "with header" = direct to api.anthropic.com, "without" = through the local forwarder that only deletesx-cc-atis):| probe (2.1.233, Fable 5, effort xhigh) | without
x-cc-atis| withx-cc-atis| Mann-Whitney (exact) ||---|---|---|---|
| light prompt (4-person bridge puzzle): responses with 0 thinking tokens | 0 / 6 | 5 / 6 (the 6th: 191) | p = 0.0022 |
| light prompt: median thinking tokens | 246 | 0 | — |
| heavier prompt (6-person puzzle), n = 3 vs 3: median thinking tokens | 974 | 408 (≈2.4× less) | p = 0.05 (max at 3v3) |
| same heavier prompt, our earlier interactive replays, n = 6 vs 6 | 971 | 438 (≈2.2× less) | p = 0.0043 |
duration_api_msscales with it (6-person puzzle: 16–22 s vs 8–11 s), so these are real thinking tokens. All 18 answers were correct in both arms, so we can't say anything about quality on real work; an explicit "ultrathink" prompt breaks the damping (1.5k–3.8k tokens with the header), so it looks like the treatment dampens the adaptive decision to think rather than blocking forced deep thinking. Arm assignment is inferred from the base URL (direct ⇒ header sent), per the bisect above.Practical consequence: the forwarder/2.1.227 workaround is quality-relevant, not cosmetic. Details, scripts and probe transcripts available on request.
TL;DR: Reproduced the same issue. Use the hook at the end to fix
Independent confirmation of @JeffvonD's root cause, from a different machine/account (native CLI on macOS, claude.ai subscription via OAuth,
claude-fable-5[1m],effortLevel: xhigh,showThinkingSummaries: true,verbose: true).Session history (all
thinkingblocks in~/.claude/projects/*/*.jsonl, split by whether thethinkingstring is non-empty):| CLI | model | date | non-empty | empty |
|---|---|---|---:|---:|
| 2.1.232 | Fable 5 | 08-14 | 169 | 0 |
| 2.1.232 | Opus 5 | 08-14 | 372 | 0 |
| 2.1.233 | Fable 5 | 08-15..08-17 | 0 | 398 |
| 2.1.233 | Opus 5 | 08-15..08-17 | 1033 | 0 |
| 2.1.234 | Fable 5 | 08-17 | 0 | 36 |
On the wire. Local logging forwarder on
ANTHROPIC_BASE_URLwith_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL=1, one-shot-prequests:| client / model |
x-cc-atisheader ||---|---|
| 2.1.234 / Fable 5 | present (16-hex value) |
| 2.1.234 / Opus 5 | absent |
| 2.1.232 binary / Fable 5, before its client-data cache refreshed | absent |
| 2.1.232 binary / Fable 5, after an interactive session refreshed it | present (same value) |
Source of the value on this machine matches the description above:
~/.claude.json→clientDataCacheSlots.bi1-<hash>.data.atis, present only in the Fable 5 slots written by 2.1.233+ and sitting next toexperimentKey: "claude_code_<redacted>_experiment"; the Opus 5 slots have neither key. The 2.1.232-era Fable 5 slot had neither key either — until I started an interactive 2.1.232 session later, at which point that slot was refreshed, receivedatis+ the experiment key, and 2.1.232 began sending the header (and returning empty summaries) as well. So the assignment is not gated on client version; an older binary only helps for as long as its cached client data predates the assignment. Checking release binaries from downloads.claude.ai confirms there is no recent version to go back to: the header-sending fetch-wrapper code and theatisgetter are present unchanged in every version from 2.1.224 through 2.1.234 (2.1.230 wasn't downloadable). What changed on 08-15 is the server starting to deliver the assignment, not the client.Two more behaviors worth noting for anyone experimenting:
~/.claude.jsonon every request, so deleting the cachedatistakes effect live in running sessions — summaries reappear on the next turn, no restart needed./v1/messagescall) and came back with the identical value each time here, so it's a stable server-side assignment, not a per-session draw.In the 2.1.234 bundle the fetch wrapper does (deminified):
if (isFirstParty) { const v = getClientDataAtis(); if (v !== undefined) headers.set("x-cc-atis", v) }, andgetClientDataAtis()just readsclientData?.atisfrom that cache — no client-side computation, no model check.Causal test on this account. Same prompt (4-person bridge puzzle),
claude -p --model claude-fable-5 --effort xhigh --thinking-display summarized --output-format stream-json --verbose, run through the forwarder; body carriesthinking: {"type":"adaptive","display":"summarized"}in every case. Only difference between arms is whether the forwarder deletesx-cc-atis:| run |
x-cc-atisforwarded upstream | thinking block on the wire |output_tokens(result event) ||---|---|---|---:|
| 2.1.234, header kept | yes |
thinking: ""| 85 || 2.1.234, header stripped | no | summary text, 302 chars | 216 |
| 2.1.234, header stripped (repeat) | no | summary text, 292 chars | 143 |
| 2.1.232 binary, stale cache (no header yet) | no | summary text, 139 chars | 37 |
Answer was correct (17) in every arm. (The
output_tokenscolumn is the whole-response count, internal thinking included; the dedicatedthinking_tokensmeasurement is in the next section.)Less thinking replication. Testing with the
x-cc-atisheader vs. without it (repro scripts below):<table>
<tr><th colspan="2">prompt (n=8 vs 8)</th><th>with header</th><th>without header</th></tr>
<tr><td rowspan="2">light (4-person bridge)</td><td>thinking_tokens</td><td><b>0 in 8/8 runs</b></td><td>median 77, never 0</td></tr>
<tr><td>median API duration</td><td>1.8s</td><td>6.0s</td></tr>
<tr><td rowspan="2">heavy (6-person bridge)</td><td>thinking_tokens</td><td>median <b>444</b> (305-578)</td><td>median <b>696</b> (530-814)</td></tr>
<tr><td>median API duration</td><td>6.4s</td><td>12.5s</td></tr>
</table>
<details>
<summary>Repro scripts (forwarder + batch)</summary>
fwd_exp.py:batch.sh:</details>
Net: the same client version, account, prompt and request body get summaries back iff
x-cc-atisis removed, so this reproduces as a server-side treatment keyed on that experiment assignment for Fable 5. Workarounds confirmed here (pinning an older binary is not one — see above):ANTHROPIC_BASE_URLthat deletesx-cc-atis;SessionStarthook that strips theatiskeys fromclientDataCacheSlotsin~/.claude.json(plus a short background re-strip loop to cover the startup re-fetch). Confirmed here: on a fresh interactive session the startup re-fetch wroteatisand the loop stripped it 1s later — thanks to the live re-read above, summaries were present from the first turn. Setup:<details>
<summary>SessionStart hook setup</summary>
Save as
~/.claude/hooks/strip-x-cc-atis.shandchmod +xit:Register it in
~/.claude/settings.json:</details>