Claude extended thinking budget exceeded — reasoning tokens over max_tokens (2026) — Fix Guide (2026) | AI Error Hub
Home Providers Claude Thinking budget exceeded
Claude Reasoning · Extended Thinking Severity: Medium HTTP 400

Claude extended_thinking — budget exceeded, no final answer returned

Extended thinking (Claude 3.7+ and 4.x) counts against your <code>max_tokens</code>. When reasoning drains the budget, the response finishes with <code>stop_reason: "max_tokens"</code> and zero output text — this page explains how to size the split correctly.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: When thinking.type = "enabled", Claude generates reasoning tokens before the final answer. Both share max_tokens. If thinking.budget_tokens is set close to or equal to max_tokens, the model may exhaust its budget on reasoning and return no visible answer, or truncate mid-answer. Fix by (a) sizing max_tokens as thinking.budget_tokens + expected_answer_tokens + 500, (b) reading stop_reason to detect truncation, and (c) never setting the thinking budget above 60-70% of max_tokens.

Real error messages you'll see

These are the exact strings returned by the Claude API service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.

Python SDK — truncated with no output text
Message(
    id="msg_...",
    stop_reason="max_tokens",
    content=[
        ThinkingBlock(type="thinking", thinking="The user asked... I should first ..."),
        # no TextBlock — response was cut before generating
    ],
    usage=Usage(input_tokens=124, output_tokens=8000, cache_creation_input_tokens=0)
)
400 — budget larger than max_tokens
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'thinking.budget_tokens (10000) must be less than max_tokens (8000).'}}
400 — budget below minimum
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'thinking.budget_tokens must be at least 1024 when thinking is enabled.'}}

Reference

How the budget splits

Reasoning tokens and output tokens both count against max_tokens. The model tries to leave room but is not guaranteed to.

SettingWhat it controlsTypical valueFailure mode when too low
thinking.budget_tokensSoft cap on reasoning4,000 – 16,000Reasoning cut short → weaker answer
max_tokensHard cap on reasoning + answerbudget × 1.5 to 2×Answer truncated or missing
answer headroom (implicit)max_tokens − budget − prompt_effect2,000 – 8,000Cannot fit final answer

Which Claude models support extended thinking

Model familyExtended thinkingDefault budgetNotes
Claude 4.6 / 4.7 OpusYesNone — must opt inLongest reasoning window
Claude 4.6 / 4.7 SonnetYesNone — must opt inBest cost/quality for reasoning
Claude Haiku 4.5YesNone — must opt inFast reasoning; small budget
Claude 3.5 Sonnet / HaikuNon/aNon-thinking family
Claude 3 (retired)Non/aMigrate — see page 90

Root causes, ranked by frequency

Based on developer reports across Claude API forums, GitHub issues, and Anthropic community during 2025–2026.

  • 28%
    budget_tokens set equal to or greater than max_tokens. Off-by-one at request build time; hard 400.
  • 22%
    Complex prompt drains most of the budget on reasoning. Model spends 7,900 of 8,000 tokens thinking, returns 100 tokens of answer or nothing at all.
  • 16%
    No headroom for the answer. max_tokens = 8,000, budget = 7,500 leaves 500 for the answer — insufficient for anything but yes/no.
  • 12%
    Streaming client not distinguishing thinking blocks from text blocks. UI shows nothing because thinking blocks arrive first and are not rendered as text.
  • 8%
    Reasoning explored a dead end. Model spent budget on wrong track; noticed too late; ran out of budget backing up. Prompt engineering issue.
  • 7%
    Budget too low for the task complexity. Multi-step math or code review with a 1,024-token budget produces empty or shallow reasoning.
  • 5%
    Retry with same params. Non-determinism helps sometimes but is not a real fix — expected to fail on the same shape.
  • 2%
    Interleaved thinking (Claude 4.6+) misconfigured. Model uses thinking between tool calls; each thinking block eats budget.

Fixes — copy-paste solutions

Fix #1

Size max_tokens using the budget-plus-headroom formula

The single formula that eliminates most extended-thinking failures.

Compute max_tokens = budget_tokens + expected_answer_tokens + safety_margin. Never set them equal. Never let budget exceed 70% of max_tokens for open-ended tasks.

size_thinking.py
"""Size max_tokens for extended thinking, safely."""
import anthropic

client = anthropic.Anthropic()

def call_with_thinking(prompt: str,
                      task_complexity: str = "medium",
                      expected_answer_tokens: int = 2000):
    """
    task_complexity: 'simple' | 'medium' | 'complex' | 'research'
    Determines the reasoning budget.
    """
    budgets = {
        "simple":   2_000,   # yes/no with light reasoning
        "medium":   6_000,   # code review, single-hop analysis
        "complex": 16_000,   # multi-hop planning, math
        "research": 32_000,  # deep reasoning tasks (Opus recommended)
    }
    budget = budgets[task_complexity]
    safety_margin = 500                              # room for token accounting slop
    max_tokens = budget + expected_answer_tokens + safety_margin

    # Never let budget exceed 70% of max_tokens
    if budget / max_tokens > 0.70:
        max_tokens = int(budget / 0.70) + safety_margin

    return client.messages.create(
        model="claude-opus-4-7",
        max_tokens=max_tokens,
        thinking={"type": "enabled", "budget_tokens": budget},
        messages=[{"role": "user", "content": prompt}],
    )

response = call_with_thinking(
    "Prove that there are infinitely many prime numbers, from scratch.",
    task_complexity="complex",
    expected_answer_tokens=3000,
)

# Extract the final text (skip thinking blocks)
text = "".join(b.text for b in response.content if b.type == "text")
reasoning = "".join(b.thinking for b in response.content if b.type == "thinking")
print(f"Reasoning: {len(reasoning)} chars; Answer: {len(text)} chars")
print(f"stop_reason: {response.stop_reason}")
For simple factual questions do not enable thinking at all — the overhead never pays off. Reserve extended thinking for tasks where the model would otherwise produce a shallow first-try answer.
Fix #2

Detect truncation from stop_reason and retry with a larger budget

Turn silent quality regressions into visible retries.

Always inspect stop_reason. If it is max_tokens, the answer is truncated (or missing). Retry with a bigger max_tokens before returning to the user.

retry_on_truncation.py
import anthropic
from anthropic.types import Message

client = anthropic.Anthropic()

MAX_ESCALATIONS = 2  # bail after two retries to keep cost bounded

def call_with_escalation(prompt: str,
                         initial_budget: int = 6_000,
                         initial_answer_headroom: int = 2_000) -> Message:
    budget = initial_budget
    headroom = initial_answer_headroom

    for attempt in range(MAX_ESCALATIONS + 1):
        response = client.messages.create(
            model="claude-opus-4-7",
            max_tokens=budget + headroom + 500,
            thinking={"type": "enabled", "budget_tokens": budget},
            messages=[{"role": "user", "content": prompt}],
        )

        if response.stop_reason != "max_tokens":
            return response

        # Grew reasoning by 50%, answer headroom by 2x
        budget = int(budget * 1.5)
        headroom = int(headroom * 2)
        print(f"[attempt {attempt}] truncated — retrying with "
              f"budget={budget}, headroom={headroom}")

    # Gave up — return the truncated response but flag it
    return response

resp = call_with_escalation("Design a distributed rate limiter for 100M req/day.",
                            initial_budget=8000, initial_answer_headroom=3000)
print("stop_reason:", resp.stop_reason)
Cap retries at 2. Some tasks are genuinely unbounded — an escalation loop that runs unchecked can consume $50 in Opus tokens on a single request.
Fix #3

Stream and route thinking vs text blocks separately in your UI

Users need to see something during the long reasoning pause.

Extended thinking can take 30-120 seconds silently. Stream the response and emit distinct UI events for thinking vs answer so the user sees progress.

stream_thinking.py
import anthropic

client = anthropic.Anthropic()

def stream_with_thinking_events(prompt: str):
    """Yields {"type": "thinking"|"text"|"stop", ...} events."""
    with client.messages.stream(
        model="claude-opus-4-7",
        max_tokens=12_000,
        thinking={"type": "enabled", "budget_tokens": 8_000},
        messages=[{"role": "user", "content": prompt}],
    ) as stream:
        current_block_type = None

        for event in stream:
            if event.type == "content_block_start":
                current_block_type = event.content_block.type
                if current_block_type == "thinking":
                    yield {"type": "thinking_start"}
                elif current_block_type == "text":
                    yield {"type": "text_start"}

            elif event.type == "content_block_delta":
                delta = event.delta
                if delta.type == "thinking_delta":
                    yield {"type": "thinking", "delta": delta.thinking}
                elif delta.type == "text_delta":
                    yield {"type": "text", "delta": delta.text}

            elif event.type == "message_stop":
                yield {"type": "stop", "stop_reason": stream.current_message_snapshot.stop_reason}

# UI-side rendering
for event in stream_with_thinking_events("Solve: what is 17 * 43 without using multiplication."):
    if event["type"] == "thinking_start":
        print("[thinking...]", end="", flush=True)
    elif event["type"] == "thinking":
        # For internal debug or dedicated UI panel; do NOT show in main output
        pass
    elif event["type"] == "text_start":
        print("\n[answer:]\n", end="")
    elif event["type"] == "text":
        print(event["delta"], end="", flush=True)
    elif event["type"] == "stop":
        print(f"\n[stop_reason: {event['stop_reason']}]")
Show users a "Claude is thinking..." indicator during thinking blocks. If the thinking finishes and text starts, transition to the streaming answer. If stop_reason arrives without any text delta, surface the "budget exceeded" state.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Use the formula: max_tokens = budget_tokens + expected_answer_tokens + 500.
  • Keep thinking.budget_tokens at ≤70% of max_tokens for open-ended tasks.
  • Only enable extended thinking on tasks where quality demonstrably improves — measure first.
  • Route thinking blocks to a UI panel or discard for output; only render text blocks in the answer.
  • Always inspect stop_reason; retry with escalated budget on max_tokens.
  • Set an escalation cap (2 retries) to prevent runaway Opus cost on unbounded tasks.
  • Log per-request thinking token counts — patterns reveal when the prompt is inducing bad reasoning paths.

Frequently asked questions

Yes — thinking tokens are billed at the standard output-token rate for the model. On Opus 4.7 that is currently around $75 per million tokens, so a 16K reasoning burst costs about $1.20 per request. Budget aggressively.
Cached prompts still work with extended thinking — the input tokens hit cache normally. The reasoning and output are always regenerated. So caching mostly helps input costs, not output.
On Claude 4.6+, thinking can be interleaved between tool calls — the model thinks, calls a tool, sees the result, thinks again, etc. Each thinking block consumes budget separately. For multi-tool agent workflows, budget 2-3× what you would for a single-shot task.
Depends on your product. For dev tools and research assistants, showing thinking builds trust and helps debugging. For consumer chat, thinking often confuses users and doubles response length. When in doubt, route thinking to a collapsible panel.
The budget_tokens field is a soft cap — the model tries to stop by then but may overshoot slightly. To enforce a hard cap, size max_tokens to be exactly what you can afford; the model is forced to stop at that boundary regardless of the budget setting.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.