The Opus 5 migration guide is honest about the easy part: change the model ID and you're done.

model = "claude-opus-4-8"  # before
model = "claude-opus-5"    # after

That's the whole required edit. The trouble is the two behavior changes that ride along — the kind a find-and-replace across your codebase won't flag, and that surface as either a truncated answer or an outright 400. Both are cheap to fix once you know they're there.

Breaking change 1: thinking is on by default#

On Opus 4.8, a plain request ran without thinking unless you explicitly set thinking: {"type": "adaptive"}. On Opus 5, that same request thinks by default — the model decides when and how much to think each turn, and effort is the dial for how deep.

The trap is max_tokens. It's a hard limit on total output — thinking tokens plus visible text. A max_tokens you tuned for a text-only workload on 4.8 now has to cover thinking too, so a value that was comfortable can start truncating the visible answer.

Fix: revisit max_tokens on any workload that previously ran without thinking. Raise it, and for xhigh/max effort give it real headroom — start around 64k and tune down.

Breaking change 2: you can't disable thinking at high effort#

This is the one that returns a hard error. On Opus 5:

# 400 error on Opus 5
client.messages.create(
    model="claude-opus-5",
    max_tokens=8192,
    output_config={"effort": "xhigh"},
    thinking={"type": "disabled"},   # rejected at xhigh / max
    messages=[...],
)

thinking: {"type": "disabled"} is only accepted when effort is high or below. Combine it with xhigh or max and the request 400s. On Opus 4.8 the two settings were independent, so any code that disabled thinking while pushing effort high is now broken.

Fix, pick one:

And a recommendation from the docs worth heeding: prefer keeping thinking on. With it disabled, Opus 5 can occasionally emit a tool call as plain text instead of a proper tool_use block, or leak internal XML tags into its visible output. If your goal was cost, lower the effort level instead — it's cleaner and cheaper.

Two behavior shifts that aren't errors — but will surprise you#

Neither of these throws, but both change output you may be asserting on:

The upside you get for free#

Two changes reward you for touching nothing:

Migration checklist#

  1. Swap the model ID → claude-opus-5.
  2. Raise max_tokens on anything that ran without thinking on 4.8.
  3. Grep for "type": "disabled" paired with effort xhigh/max → fix the pairing.
  4. Delete carried-over "verify your work" instructions.
  5. Run an effort sweep and re-baseline max_tokens and cost per completed task.

Five steps, one afternoon. The model ID is the migration; the two breaking changes are the part that actually needs your eyes.