You switched your Claude API calls to a newer model, and now you get the error below. Or the response text comes back empty. The cause is usually the same: the first content block (content[0]) is no longer text but a thinking block.

AttributeError: 'ThinkingBlock' object has no attribute 'text'

If you read response.content[0].text with the Python SDK, you get this error. In JavaScript, the same code returns undefined, so instead of failing, an empty string flows through your app. The tricky part: with simple inputs the first block is still text, so tests that use light inputs pass.

The problem in three lines

What happens

The first block is no longer text

Mainly with heavy inputs, a thinking block comes first in the response (sometimes with light inputs too). content[0].text breaks.

Cause

It thinks without being asked

Claude 5 and later models think when needed even if you don't set thinking. Claude 4 models don't think unless you set it.

Fix

Find blocks by type

Don't assume the first block. Collect the blocks whose type is "text", and check stop_reason too.

1. When it happens

The typical case is a migration where you only change the model name. For example, you move from Claude Sonnet 4.6 to Claude Sonnet 5.5 by rewriting just the model value. Assuming the response shape is the same, you leave the rest of the code alone. Then the following happens (the third case happens even with correctly written code).

How you read itWhat happens
content[0].text in the Python SDKFails with AttributeError: 'ThinkingBlock' object has no attribute 'text'
content[0].text in JavaScriptNo error; it becomes undefined, and empty text ends up on screen or in storage
Searching for the text blockIf the model thinks a lot and hits max_tokens, there may be no text block at all

The third case happens even with correctly written code. Thinking tokens count toward max_tokens, so if the limit is small, the response is cut off before the model writes any text.

2. Cause: Claude 5 and later think without being asked

Anthropic's "Thinking" documentation lists, model by model, what happens when the request doesn't include thinking. The dividing line is Claude 5.

ModelWithout thinkingThinking content
Claude 5 and later
(Opus 5.5, Sonnet 5.5, Haiku 5.5, Fable 5.1, etc.)
Thinks (adaptive thinking)Empty by default (display: "omitted")
Opus 4.8, Opus 4.7Doesn't thinkThinks only when you set thinking: {type: "adaptive"}; content is empty by default ("omitted")
Opus 4.6, Sonnet 4.6Doesn't thinkThinks only when you set thinking: {type: "adaptive"}; returns a summary by default ("summarized")

Source: the per-model table and the description of display in Anthropic's "Thinking" (checked November 1, 2026). Opus 4.5, Sonnet 4.5 and Haiku 4.5 don't accept {type: "adaptive"}; to make them think, use {type: "enabled"} with budget_tokens.

The model decides for each input whether to think. The "Steering thinking" documentation explains that for simple factual questions, the model may answer without a thinking block. It then cautions you not to build your application logic on the assumption that every response starts with a thinking block. In other words, the first block is sometimes text and sometimes thinking. Code that assumes either one will break somewhere.

The response content is ordered as shown below. The thinking content is empty by default, but the block itself is still returned.

3. What I found in testing

I tested this in my own environment with the Python SDK (anthropic 1.14.0) on November 1, 2026. I sent four models a light input and a heavy input once each, both without thinking and with {type: "adaptive"}. I set max_tokens to 8000. The light input was a simple factual question asking for the capital of Japan in a single word (we asked it in Japanese); the heavy input was a problem asking for every four-digit integer that meets five conditions.

Modelthinking settingcontent (light input)content (heavy input)
Sonnet 4.6Not settexttext
Sonnet 4.6adaptivetextthinking only (hit the limit)
Haiku 5.5Not settextthinking only (hit the limit)
Haiku 5.5adaptivetextthinking only (hit the limit)
Sonnet 5.5Not settextthinking → text
Sonnet 5.5adaptivetextthinking → text
Opus 5.5Not settextthinking → text
Opus 5.5adaptivethinking → textthinking → text

Red marks the combinations where content[0].text raised AttributeError: 'ThinkingBlock' object has no attribute 'text'. Each is the result of a single run; whether the model thinks can vary from run to run, even with the same input.

Three takeaways.

First, Claude 5 and later models thought on the heavy input even without thinking. With Sonnet 4.6, leaving it out kept text as the first block. This confirms what the documentation says: changing only the model name changes the shape of the response.

Second, with the light input, the first block was text in almost every combination. If your tests use only light inputs, you won't find this problem. On the other hand, Opus 5.5 with adaptive thought even on the light input. On Claude 5 and later, leaving out thinking and setting adaptive are the same configuration, so this difference is most likely run-to-run variation. You can't say "light inputs don't trigger thinking."

Third, when the limit was reached, no text came back at all. On the heavy input, Haiku 5.5 spent all 8000 tokens on thinking, both without thinking and with adaptive, and returned no text block. Sonnet 4.6 with adaptive also hit the limit. On Claude 5 and later the thinking content is empty by default too, so users see nothing, yet you still pay for the output tokens.

4. How to fix it

The fix is to stop assuming which block comes first and collect the blocks whose type is text (type is "text"). Also check stop_reason so you don't treat a response that was cut off at the limit as a normal answer.

Python

import anthropic

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=16000,
    messages=[{"role": "user", "content": prompt}],
)

# Don't assume the first block: collect only the text blocks
text = "".join(b.text for b in response.content if b.type == "text")

if response.stop_reason == "max_tokens":
    # Cut off at the limit. text may be empty
    raise RuntimeError("Hit max_tokens. Raise the limit or reduce thinking")
if response.stop_reason == "end_turn" and not text:
    # In tool-use flows, stop_reason is "tool_use" and empty text is normal
    raise RuntimeError("No text block")

TypeScript

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const response = await client.messages.create({
  model: "claude-sonnet-5-5",
  max_tokens: 16000,
  messages: [{ role: "user", content: prompt }],
});

// Narrow by type before reading text
const text = response.content
  .filter((b): b is Anthropic.TextBlock => b.type === "text")
  .map((b) => b.text)
  .join("");

if (response.stop_reason === "max_tokens") {
  throw new Error("Hit max_tokens. Raise the limit or reduce thinking");
}
// In tool-use flows, stop_reason is "tool_use" and empty text is normal
if (response.stop_reason === "end_turn" && text === "") {
  throw new Error("No text block");
}

In the TypeScript SDK, the elements of content are a union of types such as text, thinking and tool use. Writing content[0].text directly should normally fail type checking. But if you force the type with as, the mistake slips through.

Set max_tokens with thinking included

The "Steering thinking" documentation explains that max_tokens is a combined limit for thinking and the answer text. It also notes that a limit chosen on the assumption that the model won't think is often too small for difficult inputs. When you get stop_reason: "max_tokens", there are two fixes.

The 16000 in the examples above is a rough guide. In my tests, even 8000 was sometimes used up entirely by thinking. For production, run heavy inputs and set the value by looking at usage.output_tokens_details.thinking_tokens (the number of tokens spent on thinking) in the response.

  • Raise the limit: when thinking improves answer quality on the inputs that got cut off
  • Lower the effort: when the model is just overthinking and answer quality doesn't change (set effort with output_config.effort)

When continuing a conversation, pass thinking blocks back too

When you append a response to the conversation history, don't extract only the text; pass content back as is. Especially in tool-use flows, the thinking block holds the reasoning behind the tool call. The documentation asks you to return the blocks you received without modification, and modified blocks are rejected with a 400 error. It's safest to keep the text you display separate from the content you pass back in the history.

5. Can you just turn thinking off?

It's tempting to think "if thinking gets in the way, just turn it off," but whether you can depends on the model. Here is how thinking: {type: "disabled"} is handled.

Model{type: "disabled"}
Opus 5.5, Fable 5.1, Fable 5400 error (can't turn it off)
Sonnet 5.5400 error (instead, {type: "between_tools"} skips the initial thinking when effort is high or lower)
Haiku 5.5, Opus 5Can turn it off (only when effort is high or lower; 400 error at xhigh and max)
Sonnet 5Can turn it off

Source: Anthropic's "Thinking" (checked November 1, 2026). You can also check whether a model accepts it with capabilities.thinking.types.disabled.supported in the Models API. The default effort is medium for Opus 5.5 and Haiku 5.5 and high for most other models, so the defaults satisfy the "high or lower" condition.

Even on models where you can turn it off, doing so may lower answer quality on difficult inputs. And the same problem comes back every time you switch models. The real fix is code that can read a response in any order. If you want to reduce thinking, lower the effort first. According to Anthropic's documentation, asking Sonnet 5.5 in the system prompt to think less doesn't reliably reduce thinking, and Haiku 5.5 kept thinking even when told to answer directly (in Anthropic's testing). For both, adjust with effort.

6. How not to miss it in testing

This problem slips past tests with light inputs. When switching models, checking in the following order makes it harder to miss.

  1. Find content[0] in your code: list every place that reads by position, such as content[0], content[-1] and .content[0].text, not only in production code but also in tests and logging
  2. Add heavy inputs to your tests: include your longest production inputs and hardest tasks as test inputs (with light inputs alone, the first block stays text and the breakage stays hidden)
  3. Feed fake responses that start with a thinking block: in unit tests that don't call the API, prepare a [thinking, text] response and a thinking-only response with stop_reason "max_tokens", so you can test regardless of whether the model thinks
  4. Roll out gradually: don't switch everything at once; move some workloads to the new model first, watch the counts of empty text and exceptions, then expand

⚠️ When several services or teams switch models together, add one line to the migration request: "The first block of the response isn't always text. Find it by type." The change looks like a simple rename, and if you wait for each team to notice on its own, code that passed light-input tests will break in production.

That isn't the only thing to check when switching models. For what changed when migrating from earlier generations, see "Claude Opus 4.7 Migration Guide" and "The 3 Breaking Changes in Claude Fable 5.1." Effort in the API is covered in Anthropic's "Effort" documentation, and choosing effort in Claude Code in "Claude Code's Effort Setting."

Summary

  • AttributeError: 'ThinkingBlock' object has no attribute 'text' occurs when the first block of the response is a thinking block and you try to read the text with content[0].text
  • Claude 5 and later models think when needed even without thinking, so simply changing the model name from a Claude 4 model changes the shape of the response
  • With light inputs the first block is usually still text, so light tests pass
  • The fix is to collect the blocks whose type is "text" and to check whether stop_reason is "max_tokens"
  • Some models can't turn thinking off, so the real fix is not to turn it off but to write code that reads a response in any order

FAQ

Q. I never set thinking. Why do I get a thinking block?

A. On Claude 5 and later models, adaptive thinking is on even without thinking, and the model thinks on inputs where it decides it needs to. The thinking content is empty by default, but the block itself is included in the response. Claude 4 models such as Claude Opus 4.8 and Sonnet 4.6 don't think unless you set it.

Q. Why do I get a response with no text block at all?

A. Thinking tokens count toward max_tokens, and the limit was reached before the model wrote any text. stop_reason should be "max_tokens". Raise the limit, or lower the effort to reduce thinking.

Q. What if I want to read the thinking content?

A. Set thinking: {type: "adaptive", display: "summarized"} to get a summary of the thinking. The default on Claude 5 and later is "omitted" (the content is empty and only the signature is included). Either way, you are billed for all the thinking tokens.

Update log

  • November 1, 2026First version. Written after reviewing Anthropic's "Thinking" and "Steering thinking" documentation and testing the response order of four models in my own environment.

Sources