The migration note reads like the easiest upgrade of the year: change claude-sonnet-4-6 to claude-sonnet-5, and you are done. Same request shape, same tool definitions, same response envelope. Anthropic ships Sonnet 5 as a drop-in, and for most requests it is.
The trouble is the requests where it isn't — and they fail quietly, as a reply that stops mid-sentence, not as an exception you can catch. If you run agents in a loop, this is the migration to do with your eyes open.
The one change that truncates your answers#
On Sonnet 4.6, a request with no thinking field ran without a reasoning pass. On Sonnet 5, adaptive thinking is on by default: the same request now thinks before it answers. That is usually an upgrade — better plans, better tool use — and it is also the thing that quietly breaks output-sized workloads.
Here is why. max_tokens is a hard limit on total output, and on Sonnet 5 total output means thinking tokens plus response text. On 4.6 your budget only had to cover the answer. Now it has to cover the reasoning too. If you tuned max_tokens snugly around your expected reply — a common move for structured extraction, classification, short summaries — the model can spend that budget thinking and get cut off before it finishes the part you actually read.
A max_tokens you set for Sonnet 4.6 was sized for an answer. On Sonnet 5 it is sized for an answer plus however long the model decided to think — and it will spend the budget in that order.
Then the second force stacks on top. Sonnet 5 ships a new tokenizer that emits roughly 30% more tokens for the same text. So even holding thinking aside, the answer you were comfortably fitting under a ceiling is now ~30% "larger" in token terms. Two independent changes, one symptom: truncation. Recount your prompts with token counting before you assume any tight budget still holds.
The fix is a knob, not a rewrite#
You have two levers, and which you reach for depends on whether you wanted thinking:
- Keep thinking, make room for it. Raise
max_tokensso the ceiling comfortably covers reasoning plus your longest expected reply. This is the right call for anything where the quality bump is worth the extra output tokens. - Never wanted thinking here. Pass
thinking: {type: "disabled"}and you are back to 4.6-style behavior: the model answers without a reasoning pass. This is the right call for cheap, high-volume, latency-sensitive calls — the classify-this-row work where a reasoning pass is pure cost.
If you want thinking but need to control its depth, that knob moved too. Manual extended thinking — thinking: {type: "enabled", budget_tokens: N} — was deprecated on 4.6 and is removed on Sonnet 5; it now returns a 400. Use adaptive thinking with the effort parameter instead.
Three requests that now 400#
A pure model-string swap can still break a call that sets parameters Sonnet 5 no longer accepts. Audit for these before you ship:
# All fine on Sonnet 4.6. All 400 on Sonnet 5:
resp = client.messages.create(
model="claude-sonnet-5",
temperature=0.2, # non-default sampling param → 400
thinking={"type": "enabled",
"budget_tokens": 8000}, # manual extended thinking → 400
max_tokens=1024, # legal, but now caps thinking + reply
messages=[...],
)
Setting temperature, top_p, or top_k to any non-default value returns a 400 — remove them and steer behavior through the system prompt instead. (Assistant-message prefilling was already unsupported on 4.6, so if you migrated cleanly to that model you are fine there.) The corrected call is smaller, not bigger:
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096, # room for thinking + a full reply
messages=[...],
# thinking defaults to adaptive; add effort to tune it, or
# thinking={"type": "disabled"} to opt out entirely
)
One more branch: refusals arrive as success#
Sonnet 5 is the first Sonnet-tier model with real-time cybersecurity safeguards, and a refusal comes back as a successful HTTP 200 with stop_reason: "refusal" — not an error. Code that only checks the HTTP status will read a refusal as a success with a thin body. If you branch on status alone, add a branch on stop_reason.
The 20-minute version#
Swap the model ID. Then, in order: recount your prompts under the new tokenizer; raise max_tokens (or disable thinking) wherever an output budget was sized tight to the reply; delete non-default temperature/top_p/top_k; migrate any budget_tokens to effort; and branch on stop_reason for refusals. None of it is a rewrite. All of it is the difference between an upgrade and a week of "why did the agent stop talking."
The price story is a separate trap worth reading before you commit a fleet: the rate card is unchanged at $3/$15, but the same tokenizer that reshapes your max_tokens also reshapes your bill — we did that math in Sonnet 5's tokenizer tax, and walked the founder's-eye view in Sonnet 5 as the cheaper way to run agents. If you are moving a whole agent across at once, our general playbook for migrating an agent to a new LLM covers the eval scaffolding that catches truncation before your users do.



