max_tokens: 5 Recipes That Ship (2026)
max_tokens caps output; it does not push the model to fill it. Detect truncation via stop_reason of max_tokens, continue a cut-off answer, and leave room for extended thinking. Five copy-paste recipes for 2026.
Updated on August 11, 2026
On this page
Quick Answer (2026): max_tokens is a hard ceiling on how many tokens a model may generate for one turn. It is not a target. Setting it high does not make Claude write more; the model still stops at end_turn when it is done, and you pay only for the tokens it actually generates. The danger is the other direction: when a response hits the cap, the API returns stop_reason of "max_tokens" on a normal HTTP 200, and the text is silently truncated. Check that field before you ship the output. Below are five recipes that cover detection, continuation, and sizing the cap around extended thinking.
Everything here is grounded in Anthropic's stop-reason handling docs (2026). Model ids used:
claude-sonnet-4-6, gpt-5.
The seven stop reasons, in one place
Before the recipes, the full set Claude can return in response.stop_reason (2026):
end_turn: finished naturally. The only one that means "done".max_tokens: hit your output cap. Truncated.stop_sequence: emitted one of your stop sequences.tool_use: Claude is calling a tool.pause_turn: a server-tool loop hit its iteration limit.refusal: Claude declined.model_context_window_exceeded: the response filled the model's context window.
Only end_turn and tool_use are "the model chose to stop". The rest mean something interrupted it. Treat max_tokens as an incomplete answer, never a final one.
Recipe 1: Never ship the text without reading stop_reason
Claim. A truncated answer looks exactly like a complete one until you check the flag.
Receipt.
resp = client.messages.create(model=MODEL, max_tokens=256, messages=messages)
if resp.stop_reason == "max_tokens":
raise Truncated(resp.content[0].text) # incomplete: do not treat as final
Why. max_tokens truncation is not an error. It arrives on HTTP 200 with stop_reason set to "max_tokens". If your wrapper only catches 4xx and 5xx, it will hand you half an answer and call it success.
Failure mode. Popular SDK wrappers and agent frameworks drop the field. Real bug reports show responses "silently truncated at 8,192 tokens" because the caller never inspected stop_reason (see the copilot-sdk issue, 2026). Half a JSON object then blows up three functions downstream.
Ship. One if. Read stop_reason on every call that matters.
Recipe 2: Continue a truncated answer, do not restart it
Claim. When you hit the cap on a long answer, resume from the partial instead of paying to regenerate the whole thing.
Receipt.
resp = client.messages.create(model=MODEL, max_tokens=1024, messages=messages)
if resp.stop_reason == "max_tokens":
partial = resp.content[0].text
messages.append({"role": "assistant", "content": partial})
messages.append({"role": "user", "content": "Continue from exactly where you stopped. Do not repeat."})
resp2 = client.messages.create(model=MODEL, max_tokens=1024, messages=messages)
full = partial + resp2.content[0].text
Why. Feeding the partial back as the assistant turn puts the model back at the exact cursor. This is assistant prefill in disguise: the model treats your partial as its own words and keeps writing.
Failure mode. Concatenation seams. If the cut landed mid-word or mid-token, partial + continuation can glue a broken join. For structured output, cut the partial back to the last clean boundary (last newline, last closing brace) before you resume.
Ship. Continue on max_tokens, join at a clean boundary, done.
Recipe 3: Set the cap generously; it costs nothing until used
Claim. A high max_tokens is free until the model actually spends the tokens.
Receipt.
# 8192 is a ceiling, not a bill. A short answer still stops at end_turn.
resp = client.messages.create(model=MODEL, max_tokens=8192, messages=messages)
Why. You are billed for output tokens generated, not for the ceiling you allowed. A three-sentence reply under a max_tokens of 8192 costs the same three sentences it would under 512. The ceiling only changes when the model is allowed to stop being cut off.
Failure mode. Setting the cap too low to "save money" is the classic own goal. It does not save output tokens on short answers, and it silently truncates the long ones. You pay for the truncated tokens anyway, then pay again to retry.
Ship. Default the cap to comfortably above your longest expected answer. Tighten it only when you want a hard length limit as a feature.
Recipe 4: Leave room for thinking
Claim. With extended thinking, the thinking tokens count against max_tokens, so the cap must be larger than the budget.
Receipt.
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=16000, # hard ceiling for the whole turn
thinking={"type": "enabled", "budget_tokens": 10000},
messages=messages,
)
Why. Per Anthropic's extended-thinking docs (2026), budget_tokens must stay under max_tokens because reasoning tokens and answer tokens share the same ceiling. If the budget eats the whole cap, there is nothing left for the reply. max_tokens stays the hard limit; the budget is only a target.
Failure mode. A budget equal to or above the cap. On manual-thinking models the API rejects it; and even when it is accepted, a budget too close to the cap leaves the answer truncated after all the thinking. Note that on adaptive-thinking models (Opus 4.7 and later, Sonnet 5, Opus 5) budget_tokens is gone; you steer depth with effort and only size max_tokens for the answer.
Ship. Keep max_tokens well above budget_tokens. Rule of thumb: budget plus your longest expected answer, then some slack.
Recipe 5: Same trap in OpenAI, different field name
Claim. OpenAI truncates the same way; the flag is called
finish_reason and the value is "length".
Receipt.
resp = client.chat.completions.create(
model="gpt-5",
max_completion_tokens=1024,
messages=messages,
)
if resp.choices[0].finish_reason == "length":
# truncated at the cap, exactly like Claude's max_tokens
handle_truncation(resp)
Why. One mental model covers both providers. Claude returns stop_reason of "max_tokens"; OpenAI returns finish_reason of "length" (see the OpenAI chat object reference, 2026). Same meaning, same fix: detect, then continue or raise the cap.
Failure mode. Assuming a missing or "stop" finish reason means complete. On OpenAI the parameter is max_completion_tokens, not the legacy max_tokens, on newer models. Set the wrong one and your cap is ignored.
Ship. Map both flags to one is_truncated(resp) helper and stop special-casing providers.
When NOT to raise max_tokens
Raising the cap is the wrong fix when the model keeps hitting it on a task that should be short. That is a prompt problem, not a ceiling problem. A runaway loop under a bigger cap just bills you for more junk. Two cases to slip the raise:
- The output should be short but keeps maxing out. Tighten the prompt or add a stop sequence, do not widen the cap.
- You want a hard length limit as a product feature. Then
max_tokensis your tool; keep it low on purpose and treatmax_tokenstruncation as expected, not a bug.
Cost to test: about $0.01. Two short Messages API calls on claude-sonnet-4-6.
Written by
Roo IyerRoo Iyer writes terse, tested prompt and API recipes for PromptAttic. Every recipe ships with a receipt, a failure mode, and a cost to test.
FAQ
What does stop_reason "max_tokens" mean?
It means the response was cut off because it reached the max_tokens ceiling you set for that turn. It arrives on a normal HTTP 200 response, not an error. The text you got back is incomplete, so you should either raise the cap and retry or continue the response, never treat it as final.
Does setting a higher max_tokens make Claude write more?
No. max_tokens is a ceiling, not a target. The model still stops at end_turn when it has finished answering. A high cap only removes the risk of being truncated; it does not push the model to fill the space.
Do I pay for the full max_tokens even if the response is short?
No. You are billed for the output tokens actually generated, not for the ceiling you allowed. A three-sentence reply costs the same whether the cap was 512 or 8192, so defaulting the cap generously is effectively free until the model needs the room.
Why does my response get cut off mid-sentence or mid-JSON?
Because the output hit max_tokens before the model finished. The fix is to detect stop_reason equal to max_tokens, then either raise the cap or continue the response. For structured output, trim the partial back to the last clean boundary before resuming so you do not glue a broken join.
How do I continue a response that stopped at max_tokens?
Append the partial text back as an assistant message, then add a user message like Continue from exactly where you stopped. Do not repeat, and call again. Feeding the partial back as the assistant turn puts the model at the exact cursor, which is assistant prefill applied to continuation.
How does max_tokens interact with extended thinking and budget_tokens?
On manual-thinking models the thinking tokens count against max_tokens, so budget_tokens must stay under max_tokens and you should leave room for the answer on top of the budget. On adaptive-thinking models (Opus 4.7 and later, Sonnet 5, Opus 5) budget_tokens is gone; you steer reasoning depth with effort and size max_tokens for the answer only.
Related recipes
Assistant Prefill on Claude: 4 Recipes That Ship (2026)
Assistant prefill in 2026: four copy-paste recipes that force JSON, kill the preamble, lock the output shape, and hold a persona, plus where prefill breaks.
Claude Extended Thinking: 3 Recipes That Ship (July 2026)
Extended thinking changed in July 2026: on Claude Sonnet 5 and Opus 4.8 you use adaptive thinking and effort, not budget_tokens. Three recipes and the gotchas.
Stop Sequences on Claude: 4 Recipes That Ship (2026)
Stop sequences in 2026: four copy-paste recipes that cut a model off at a delimiter, bound a JSON object, force one section, and stop an agent from faking its own turn, plus where they break.