Skip to content

Anthropic

Claude’s raw thinking never leaves the server

8 min read

Written by an AI model from the linked sources and checked against them by a second model. How we work

Image: gist.github.com

A developer attached Chrome’s debugging protocol to Claude Desktop and found that the thinking field in every streamed response is an empty string. The teardown, published on GitHub, includes wire captures from September 5. What the app gets instead is one summary sentence, which a separate step produces after the reasoning has finished.

The write-up goes past the empty field. It times the gaps on the TCP connection to work out how long Claude reasoned and how many passes it made, and it gets Claude to give up the name of the internal tag the server strips out. One response that the UI labelled “Thought for 5s” turned out to contain two reasoning passes in a row, and each one got its own summary.

Not in the DOM, not on the wire

Claude Desktop is an Electron app, so Chrome DevTools Protocol (CDP) can attach to its renderer much as it would to a web page. The author ran a patched build, app-1.37937.3, with the auto-updater disabled, telemetry stripped and CDP exposed on port 9222. The first step was to dump every [data-cds="TurnStatus"] element, the container that wraps each thinking block. Injected CSS forced the collapsed panels open. Each one held the same one-line summary the toggle already showed. There were no hidden nodes, no shadow DOM and no off-screen buffer.

The network layer took longer. Pausing the stream with Fetch.enable response interception froze the app mid-sentence, because the server-sent events (SSE) stream can’t be held. Monkey-patching window.fetch in the renderer captured nothing. The author concluded that the API calls start in Electron’s main process or a preload context and never go through the renderer’s JavaScript. The extracted app bundle had no code that parses thinking markup. What finally worked was passive: Network.enable, then Network.getResponseBody after the response finished loading. That copy of the stream starts like this:

{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","summaries":[]}}

It is followed by a single thinking_summary_delta carrying one sentence. In every captured response the thinking value was "", and not one thinking_delta event appeared. The schema still has a slot for raw reasoning, but the slot is always empty. In the author’s words, it is “preserved like a room where furniture used to be”.

The tag Claude couldn’t say

Asking Claude about it in fresh sessions went nowhere. The model deflected or said it couldn’t access its own reasoning. The author attributes this to a system prompt instruction, added with the thinking restrictions, that tells Claude not to discuss thinking mechanics. That reading is the author’s and comes from behaviour, not from a prompt anyone has seen.

An old Desktop session went differently. Months earlier the author had used it to get Claude to write its answer through its thinking process. In that context Claude cooperated: it copied its reasoning into the text channel verbatim, and then did it again without an “I’m thinking:” prefix. The author takes this as confirmation that the reasoning sits in the context window during generation and is removed only before the response leaves the server.

Then the author suggested the thinking might be wrapped in a tag. Claude started to answer, “it’s definitely not a tag like `<thinking>` or `”, and the stream stopped mid-sentence, right where the next tag name would open. Next the author asked for the answer Caesar-shifted by three letters. The reply was dqwpo:wklqnlqj, which shifts back to antml:thinking. That is the same antml namespace prefix the API uses for tool-call markup. In the author’s reading, the server-side filter matches literal character sequences as tokens stream out, and shifted text doesn’t match. Claude’s own description in that session: “the infrastructure is literally intercepting and removing it mid-response.”

The author describes two layers. One is the streaming filter between the model’s output and the SSE serializer, and nothing gets past it. The other is the behavioural guardrail, which the old session and the cipher both got around.

Two passes behind one header

Since the thinking tokens are dropped before any event is sent, the connection goes quiet for as long as the model reasons. The author wrote capture-thinking-timing.mjs to log every gap over 300ms, using the per-chunk timestamps from Network.dataReceived. The first real capture found a 2,839ms gap, and the UI said “Thought for 3s”.

On September 5 the author ran three captures in the old session, each asking Claude to reproduce its thinking verbatim:

  • 20:41:58: 4,517ms in total, 57 chunks and one gap of 1,863ms. Claude’s reproduced thinking read, in part: “There’s nothing particularly deep in this pass, I’m just noting that I understand the request.”
  • 20:42:32: a 2,839ms thinking gap, then a 487ms gap before any response text arrived. The author attributes the shorter gap to the separate inference step that writes the summary line.
  • 21:00:36: a request for a short story about a lighthouse keeper, made alongside the thinking reproduction. It took 7,585ms over nine chunks and had four gaps.

The third capture is the one the write-up rests on. It shows 2,806ms of silence, then a 194-byte chunk too small to be response text, then another 3,077ms of silence before the text begins. The author reads the small chunk as a block-stop/block-start pair marking the switch from one thinking block to the next. Two more gaps of 756ms and 754ms followed, one summary per pass. The two thinking gaps add up to 5,883ms, which fits the “Thought for 5s” header. That header gives one number and one sentence, with no sign that Claude reasoned twice. The author calls it “one capture” and says it is not a study, but argues that the structure is too clean to be noise.

The summary step is not free. Anthropic’s stated reasons for hiding thinking in Claude Code were latency and clarity: showing thinking means generating extra summary text as the reasoning streams, and raw reasoning includes paths Claude later abandons. In Desktop the summary step still runs. In the 21:00:36 capture, the two gaps the author attributes to summarization come to about 1.5 seconds of a 7.6-second response.

What benvanik’s numbers measure

The write-up places all this alongside benvanik’s audit of 6,852 Claude Code session files. That audit found a 67% drop in thinking depth, from about 2,200 characters to 720, and the write-up connects it to the rollout of the redact-thinking-2026-02-12 header. Anthropic acknowledged the regressions in an April 2026 postmortem. According to the teardown, raw thinking access was never restored.

benvanik’s own gist complicates the timing. With the text redacted, it estimates depth from the signature field on thinking blocks, the same signature that Fable 5.1 now uses to reject edited histories. In 7,146 paired samples the signature correlates with content length at 0.971. On that estimate, depth had already fallen to about 720 characters by late February, which benvanik says was before redaction started. Visible thinking blocks were at 100% from January 30 to March 4 and at 0% from March 12. The rollout went 1.5%, 25%, 58% and then 100% over a single week, which benvanik reads as a staged deployment. After March 8 the gist also counts a Read:Edit ratio that fell from 6.6 to 2.0, and a stop hook that fired 173 times in 17 days after never firing before.

The Desktop teardown suggests a possible contributor, and the author frames it as a hypothesis. Thinking blocks are stripped before the conversation history goes back to the model, so on a follow-up turn Claude sees its earlier answers but not the reasoning behind them. If so, the author writes, each message “partially starts cold”, and that “might be part of” what benvanik measured.

Billed, then dropped

None of this changes what you pay. Thinking tokens are billed in full whether or not they reach the screen, so the billed count won’t match what the UI shows. In the author’s summary, the model reasons “using compute you’re paying for, and the output of that reasoning gets stripped before it reaches you.” Timing can show when Claude thought, for how long and in how many passes. It can’t show what it thought.

Claude Code is the one place where a setting changes what you see. Two separate entries in ~/.claude/settings.json are needed: "showThinkingSummaries": true lifts the redaction, and "verbose": true (or Ctrl+O) displays the result. Without the first, a subscription session gets an empty thinking block, and verbose alone just expands an empty box.

No comments yet. Be the first.

Join the conversation

Sign in to leave a comment. It appears after moderation.