Opus 5.5 in Practice: Effort Is the Only Dial Left
Every Claude Opus 5.5 request thinks, and you can no longer switch that off. What you still control is how much it thinks, through one parameter called effort, and that moves three jobs onto you: pick an effort level per kind of task, keep the prompt cache warm while you do it, and check what a long run actually changed. This is the playbook for all three, with the requests, config, and prompts to copy.
Anthropic shipped Opus 5.5 on September 22 at $4 per million input tokens and $20 per million output, down from Opus 5's $5 and $25, with cache reads at $0.20 instead of $0.50. It has a 1M token context window by default, 128K tokens of maximum output, and it is the default Opus model in Claude Code v2.1.280. The API release notes carry the line that breaks code: thinking: {"type": "disabled"} now returns a 400. Thinking is billed as output whether or not you ever see it.
This page follows one request from the wire up: the shape it must have to be accepted, the blocks that come back, the one dial that sets how much thinking you buy, and what a long run needs from your harness. Each section ends in the code or config that the mechanism implies, checked against Anthropic's docs.
1. Make the request valid
The migration guide lists what every request to claude-opus-5-5 must satisfy, and each item that says "rejected" means a 400 before a single token is generated. Send no thinking field, or {"type": "adaptive"}, which is the same thing. Use tool_choice auto or none; forcing a tool with any or tool is rejected, including on the token counting endpoint. Leave temperature, top_p, and top_k at their defaults. Do not end messages with a prefilled assistant turn. Here is the diff for a typical Opus 5 extraction route:
{
- "model": "claude-opus-5",
+ "model": "claude-opus-5-5",
- "max_tokens": 1024,
+ "max_tokens": 8192,
- "thinking": {"type": "disabled"},
+ "output_config": {"effort": "low"},
- "tool_choice": {"type": "tool", "name": "extract_invoice"},
+ "tool_choice": {"type": "auto"},
"tools": [{"name": "extract_invoice", "strict": true, "input_schema": {...}}],
"messages": [{"role": "user", "content": "..."}]
}
Why each line: the thinking field goes because the guide rejects both disabled and manual budgets, and the Opus 5.5 prompting guide says to start routes that used to run with thinking off at low and measure. max_tokens goes up because it is a hard limit on thinking plus text; a limit sized for a terse reply now truncates it. Forced tool use becomes auto plus strict tool use, and the prompt says when the tool applies. The 8192 is a starting value to tune, not a documented number.
Computer use changed shape on the Claude API and Google Cloud. The old tool returns 'claude-opus-5-5' does not support tool types: computer_20251124. The toolset takes no name, no display size, and no beta header:
- "tools": [{"type": "computer_20251124", "name": "computer",
- "display_width_px": 1024, "display_height_px": 768}],
+ "tools": [{"type": "computer_toolset_20260801"}],
Your agent loop changes with it: the action is now the tool_use block's name rather than input.action, several such blocks can arrive in one turn, and every result must echo toolset_name. Amazon Bedrock still accepts the old tool, so one codebase on two clouds needs a flag that picks the declaration. If you use Claude Code, it ships a skill that does the mechanical part of this across a repository and hands you a checklist for the rest:
/claude-api migrate this project to claude-opus-5-5
The figure lints a request the way the guide does. Pick the model it came from and the platform it runs on, then click a red line to apply the fix.
A production request body retargeted to claude-opus-5-5. Each line is checked against the migration guide's rules for the platform you pick. Red lines return a 400; amber lines run but change behavior. Click a flagged line to apply its fix.
Error strings and fixes quoted from platform.claude.com migration guide and API release notes, Sep 22, 2026. The max_tokens value in the fix is a starting point, not a documented number: the guide says to revisit it because it now covers thinking plus text, and to start at 64k for xhigh and max.
2. Handle thinking you pay for and cannot turn off
"Adaptive" describes who decides. The model chooses whether a turn needs thinking and how much, and effort biases that choice. The effort docs say lower levels can skip thinking entirely on simple problems, and that tool-result turns in a loop can skip it at any level. So always on is a floor on the mode, not on the volume. Three response-side facts follow, and each has broken a harness that never touched the thinking parameter.
Responses begin with thinking blocks. Select blocks by type. The thinking text is empty by default. thinking.display defaults to "omitted": you get a block with an empty thinking field and a signature, and you pay for the tokens anyway. Set "summarized" to read summaries. Progress notes moved into thinking blocks. The short text the model writes between tool calls used to arrive as text; now it arrives as a progress-update thinking block, empty at the default display, so an agent UI goes quiet. The "updates" display mode (beta header thinking-display-updates-2026-08-18) returns just those notes.
And one rule turns a UI bug into a hard failure: in a tool loop, thinking blocks must go back to the API complete and unmodified, empty ones included. A harness that rebuilds the assistant turn from its text and tool calls fails on the second request with a 400. The loop that gets all of this right is short:
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Fix the three failing tests in tests/api/ and run them."}]
def run_tool(block):
output = TOOL_IMPLS[block.name](**block.input)
return {"type": "tool_result", "tool_use_id": block.id, "content": output}
while True:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=64000,
thinking={"type": "adaptive", "display": "summarized"},
output_config={"effort": "medium"},
tools=TOOLS,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break
results = [run_tool(b) for b in response.content if b.type == "tool_use"]
messages.append({"role": "user", "content": results})
print("".join(b.text for b in response.content if b.type == "text"))
The line that matters most is messages.append({"role": "assistant", "content": response.content}): it echoes the turn exactly as received, which is what the API checks. The fields and values match the effort and migration docs; TOOLS and TOOL_IMPLS are yours. The figure below runs one read-then-answer turn through three harness choices so you can see which combination fails.
One agent turn on Opus 5.5 that reads a file and then answers: two API responses, rendered as the typed blocks they contain. Choose how your code reads text, which display mode it requests, and how it sends the assistant turn back with the tool result.
Block order and rules from the Opus 5.5 migration guide. Block contents are an illustrative trace; token counts are placeholders sized for a short read-then-answer turn.
3. Pick effort per task, not per model
Effort is now the only request parameter that controls thinking depth, and it also shapes everything else in the reply: the docs say it "affects all tokens in the response," and lower levels make "fewer and terser tool calls." Anthropic's prompting guide for Opus 5.5 reports that medium matched or beat Opus 5 at high on its coding and knowledge-work evaluations, and that low came close on several coding ones. That is vendor testing, so treat these as starting points and sweep on your own evals:
| Task class | Start at | max_tokens | Why |
|---|---|---|---|
| Extraction, classification, routing, subagents | low | 8k to 16k | Docs name subagents as the low use case; routes that ran thinking off start here |
| Most agentic coding and tool use | medium | 32k to 64k | The default; matched Opus 5 at high in Anthropic's testing |
| Hard debugging, complex reasoning | high | 64k | Spends what the task needs; the default on every other model |
| Runs over 30 minutes with million-token budgets | xhigh | 64k to 128k | The level the docs describe for long agentic work; 128k "has worked well" for long turns |
| Frontier problems with a measured gain | max | 128k | Reserve it for work where your evals show headroom |
The token ranges are my starting points except where quoted. Two mechanics decide whether switching levels costs you money. A new top-level effort value between requests restarts the prompt cache, because effort shapes the rendered prompt. On Opus 5.5 you can instead change it per message, which keeps the cache: add an effort-only system message, send the beta header mid-conversation-output-config-2026-07-01, and the new level applies from the next user turn.
{"role": "system", "content": [], "output_config": {"effort": "low"}}
In Claude Code the same dial lives in three places. The top-level effortLevel setting no longer applies to Opus 5.5 and newer models; they read a per-model entry in settings.json, per the model config docs:
{
"modelSettings": {
"claude-opus-5-5": { "effortLevel": "high" }
}
}
/effort xhigh # set the level for the active model: low, medium, high, xhigh, max
/effort auto # clear the saved level for this model
export CLAUDE_CODE_EFFORT_LEVEL=low # scripts and CI
If a route ran Opus 5 with thinking disabled for cost, the question is whether always-on thinking now costs more than it saves. The figure answers it for one request shape: set your token counts, then set the thinking tokens you measured at low.
One request priced three ways at list prices: Opus 5 with thinking disabled, Opus 5 with thinking on at the same thinking volume, and Opus 5.5, where thinking is always on. The rail marks the thinking volume where Opus 5.5 stops being cheaper than Opus 5 with thinking off. Thinking tokens are yours to measure; the default is a placeholder.
Prices per million tokens from anthropic.com/news/claude-opus-5-5: Opus 5 $5 in, $25 out, $0.50 cache read; Opus 5.5 $4 in, $20 out, $0.20 cache read. Cache writes, batch discounts, and fast mode are left out. Thinking tokens are billed as output tokens per the migration guide.
The break-even has a closed form. With I uncached input tokens, C cached, V visible output, and T thinking, Opus 5 with thinking off costs 5I + 0.5C + 25V per million and Opus 5.5 costs 4I + 0.2C + 20(V + T), so Tbreak-even = (I + 0.3C + 5V) / 20. Cheap cache reads push it up: a long cached prefix leaves a lot of room for thinking, while a short uncached classifier call leaves very little. If a route lands above the line at low, the prompting guide suggests one more lever before you give up on it: the system prompt line Answer directly without deliberating. reduces thinking further, and you measure quality when you add it.
4. Move reasoning control out of the prompt
On Opus 5 a prompt was one of the few ways to steer how hard the model worked. On Opus 5.5 that control has a documented home, the effort parameter, and a documented output, the thinking block. Prompt lines that tried to do either job now duplicate the mechanism, and one kind collides with a refusal category. Find them mechanically before you judge them by eye:
grep -rniE "step by step|think (carefully|hard)|show (your )?(reasoning|work)|explain your reasoning" prompts/ src/**/*.txt
| Line in the prompt | Move the job to | Why, from the docs |
|---|---|---|
| Asks for more deliberation | output_config.effort, one level up | The effort docs make effort the control for thinking depth under adaptive thinking; wording does not change the budget |
| Asks the model to print its reasoning before the answer | thinking.display: "summarized" | The migration guide lists reasoning_extraction as a refusal category for prompts that push the model to reproduce its reasoning |
| Asks for a status paragraph every N tool calls | thinking.display: "updates" (beta) | Progress notes are emitted as thinking blocks between tool calls |
| Tells the model not to think | effort: "low" | Thinking cannot be disabled; the lowest effort is the documented way to spend least on it |
Routing then becomes a table in code rather than a paragraph in a prompt. One map, read by every call site, keeps each route on one effort level for its whole conversation, which is also what keeps its cache prefix stable:
EFFORT_BY_ROUTE = {
"classify_ticket": "low",
"extract_invoice": "low",
"code_agent": "medium",
"incident_debug": "high",
"overnight_migration": "xhigh",
}
def create(route, **kwargs):
return client.messages.create(
model="claude-opus-5-5",
output_config={"effort": EFFORT_BY_ROUTE[route]},
**kwargs,
)
Pasted text is the one prompt change worth making for a new reason. The prompting guide recommends wrapping text a user pasted from elsewhere in tags with an ID your app generates, and telling the model in the system prompt that instructions inside those tags belong to the pasted source. Generate the ID per paste so a document cannot close the tag itself:
import secrets
def wrap_paste(text):
tag = secrets.token_hex(4)
return f'<pasted_content id="{tag}">\n{text}\n</pasted_content id="{tag}">'
5. Keep long runs moving
The migration guide notes that Opus 5.5 reports progress as it works, and some of those reports end a turn as text with stop_reason: "end_turn" rather than a tool call. A loop that equates end of turn with completion therefore stops partway. The fix belongs in the harness, because the harness is the only party that can compare the model's claim with the state of the work.
Give the run a machine-readable state file. A prose plan inside the conversation disappears at compaction and cannot be checked by code. A small JSON file in the repository survives both. Put the contract in CLAUDE.md:
## Run state
- The run's state lives in .run/state.json with keys "open", "done", and "blocked".
Each item is {"id": ..., "what": ..., "evidence": ...}.
- Move an item to "done" only with evidence: the test command and its result, or the file and line.
- Move an item to "blocked" only with the question that unblocks it.
- The run is finished when "open" is empty and every "done" item has evidence.
Decide continuation in code. After a text-only end of turn, the harness reads the state file. Open items and no blocked ones means the model paused, so it gets one short nudge; a blocked item means a human is needed; an empty open list means the run is done. Cap the nudges so a genuinely stuck run ends and gets reviewed:
import json
def next_step(response, nudges, cap=3):
if response.stop_reason == "tool_use":
return "run_tools"
state = json.load(open(".run/state.json"))
if state["blocked"]:
return "ask_human"
if not state["open"]:
return "done"
return "nudge" if nudges < cap else "review"
NUDGE = "Open items remain in .run/state.json. Pick the next one and act on it."
Two details from the docs affect how you set this up. Add any standing system-prompt instruction before the first request, since changing the system prompt mid-session invalidates earlier thinking blocks and restarts the cache. And Opus 5.5 responds to elapsed-time information, so a harness that appends elapsed 340s / 1200s to each message gives it a budget to pace against; treat that budget as advisory and keep your own timeout. For interactive sessions in Claude Code, the fast mode docs list Opus 5.5 at $8 and $40 per million tokens; enable it when a session starts, because enabling it later bills the existing context at the fast uncached input price.
6. Check what the run changed
A long run ends with a summary, and the summary is the model's account of its work, not the work. The checks that matter can mostly be run without a model. Start with scope and tests on a clean checkout:
git diff --stat main...HEAD
git diff --name-only main...HEAD | grep -vE '^(src/routes|test/routes)/' && echo "OUT OF SCOPE"
git stash --include-untracked && npm ci && npm test
Then review for intent, with a reviewer that never saw the author's reasoning: a fresh session or a subagent that receives only the task's definition of done and the diff. Ask it to trace each hunk to a requirement, which turns "anything suspicious?" into a question with a checkable answer:
Definition of done: {paste the task's done criteria}
For every hunk in the attached diff, name the requirement above that it serves.
Output a table: file, hunk range, requirement, or NONE.
Every NONE row is a finding. After the table, name each requirement that no hunk serves.
Hunks with no requirement are the extraneous edits long runs tend to accumulate; requirements with no hunk are the work the summary claimed but did not do. Then check the bill: in Claude Code, /context shows where the window went and /cost names the causes of cache misses, including thinking-mode and display changes since v2.1.280.
7. A worked example: migrating 38 route handlers
A realistic long run: an Express service has 38 route handlers on a callback-style database client, and the task is to move them to the promise API and keep every test green. It runs in Claude Code on API billing, so every token has a list price. The token counts below are assumptions for a run of this size, not measurements; the prices are Anthropic's.
Task: Move every handler in src/routes/ from db.query(sql, cb) to await db.query(sql).
Done means: npm test passes, no handler still passes a callback, and the diff touches
nothing outside src/routes/ and test/routes/. Keep run state in .run/state.json.
Settings: /effort medium, because this is standard agentic coding; xhigh is the alternative if a first pass shows it missing edge cases. Assume 150 model requests, each reading about 104,000 tokens of cached prefix, writing about 6,000 new tokens to the cache, and producing 900 visible tokens plus 1,600 thinking tokens at medium or 4,000 at xhigh. Cache writes cost 1.25 times the input price, which is $5 per million on Opus 5.5 and $6.25 on Opus 5.
| Setup | Per request | 150 requests |
|---|---|---|
| Opus 5.5, medium | $0.0208 read + $0.0300 write + $0.0500 out = $0.1008 | $15.12 |
| Opus 5.5, xhigh | $0.0208 + $0.0300 + $0.0980 out = $0.1488 | $22.32 |
| Opus 5, same tokens | $0.0520 + $0.0375 + $0.0625 out = $0.1520 | $22.80 |
| Opus 5.5, medium, cache rebuilt every request | $0.5500 write + $0.0500 out = $0.6000 | $90.00 |
Two things stand out. At these assumptions, the step from medium to xhigh costs about $7 on the whole run, which is cheap if it saves one round of fixes. And the cache matters more than the effort level: each time the cached prefix is thrown away, rewriting 104,000 tokens costs about $0.52 instead of $0.02 to read them. Ten top-level effort switches mid-run add about $5; editing the system prompt or the tool list every turn is the $90 row. The Opus 5 row assumes identical token counts, which is conservative for Opus 5.5, since the prompting guide says it tends to finish the same task with fewer tokens.
What to check at the end: npm test passes on a clean checkout, not only in the session; a search for calls that still pass a callback, grep -rnE "db\.query\(.*, *(function|\(err)" src/routes/, returns nothing; the scope check prints nothing; .run/state.json has an empty open list with evidence on every done item; and the traceability review has no NONE rows.
When it goes wrong
| Symptom | Cause | Fix |
|---|---|---|
400: "thinking.type.disabled" is not supported for this model. | Thinking can no longer be disabled or budgeted | Remove the field; set output_config.effort |
400: tool_choice: type "tool" and "any" are not supported | Forced tool use was removed | auto plus strict tool use; say in the prompt when the tool applies |
| 400 on the second request of every tool loop | The harness rebuilt the assistant turn and dropped thinking blocks | Append response.content as received |
| Replies cut off mid-sentence | max_tokens sized for replies without thinking | Raise it; 64k at xhigh and max, up to 128k for long agentic turns |
| Agent UI silent between tool calls | Progress notes are empty thinking blocks at the default display | display: "updates" (beta) and render those blocks |
| Unattended agent stops after a progress summary | A text-only end_turn read as completion | A state file the harness reads, plus a capped nudge |
stop_reason: "refusal", category reasoning_extraction | The prompt asks the model to write out its internal reasoning | Remove that instruction; read summarized thinking instead |
| A cheap route got more expensive | It ran Opus 5 with thinking off; it now thinks | Measure at low; place it on Fig. 3; keep it on Opus 5 if it lands above the line |
| Cache hit rate drops mid-conversation | Top-level effort changed, or the system prompt or tools were edited | Per-message effort change; keep the conversation append-only |
| A fallback model answers without context | On the Claude API, only Fable 5.1 and Mythos 5.1 read Opus 5.5 thinking blocks | Route fallbacks to those, or accept the lost reasoning |
A migration order that follows the request
Each step depends on the one before it, so the order matters more than the list. A route that still sends a disabled thinking field never gets far enough for its prompt or its effort level to matter.
- Make every request valid. Remove
thinkingfields that disable or budget thinking, drop forcedtool_choiceforautoplus strict tools, raisemax_tokens, and declarecomputer_toolset_20260801on the Claude API and Google Cloud. Fig. 1 shows what still returns a 400. - Make every response readable. Select blocks by
type, append assistant turns exactly as received, and choose thethinking.displaymode your interface can render. Fig. 2 shows which combination survives a tool loop. - Give every route an effort level. One map in code, one level per conversation, per-message changes only through the effort-only system message. Measure thinking on routes that used to run with it off and place them on Fig. 3.
- Move reasoning control out of prompts. Run the grep, move each hit to effort or display, and wrap pasted text.
- Put long runs on a state file. The CLAUDE.md contract,
next_step()in the harness, and a cap on nudges. - Check the change and the bill. Scope and tests on a clean checkout, a traceability review from a fresh reviewer, then
/costfor cache-miss causes after the first long run.
For the product side of the same launch, for chat users and interactive coding sessions, Addy Osmani's Getting the most out of Opus 5.5 is the companion read.
Keep reading