Comparison · Intermediate · 10 min
Opus 5.5 vs Sonnet 5.5 vs Haiku 5.5 in Claude Code: which to use
Which model and effort to pick in Claude Code 2.1.316, official prices, opusplan and a reproducible test with hidden tests (results pending).
Tested with Claude Code 2.1.316 · reviewed
See the files on GitHub Free · MIT license
Summary and verdict
In six weeks four new models have reached Claude Code (Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5) and, on top of that, the 5.5 models now start with medium effort. "Which one should I use?" is a daily question and the usual answer is an opinion. Here you get two things: a decision table based on the official documentation and a reproducible test (same task, same repository, hidden tests, three repetitions) to measure time, cost and quality.
Test status: methodology ready; results pending. The test costs money and has not been run yet, so this page contains no result figures at all. Once it runs, the results table will be filled in with the date and version. Until then, the verdict is based only on the official documentation:
- Day-to-day work with a clear scope: Sonnet 5.5 at
medium. It costs half as much as Opus. - Bugs in existing code, design, or when Sonnet gets it wrong: Opus 5.5 at
medium(the default) orhigh. - Simple, high-volume tasks and search subagents: Haiku 5.5 at
lowormedium. - Work bigger than one session, root cause or architecture: Fable 5.1, which may be billed with usage credits.
- Plan with Opus and implement with Sonnet:
/model opusplan.
All the test files are in the free repository ctrlia-opus-sonnet-haiku.
The four models you have in Claude Code today
On the Anthropic API the aliases point to: opus → Opus 5.5 (since 2.1.280), sonnet → Sonnet 5.5 (2.1.284), haiku → Haiku 5.5 (2.1.293) and fable → Fable 5.1 (2.1.257). All four have a 1 million token window. On Bedrock, Agent Platform and Foundry the Sonnet and Haiku aliases still point to older versions, so there you have to use the full ID.
With the default value, Pro, Max, Ultra, Team, Enterprise and the API start on Opus 5.5. Fable is never the default model and, depending on your plan, may be billed with usage credits (the picker shows "Requires usage credits"). To pin an exact version, use the ID:
claude --model claude-opus-5-5
Effort: the setting almost nobody touches
All four models accept low, medium, high, xhigh and max. Opus 5.5, Sonnet 5.5 and Haiku 5.5 start at medium (Opus 5 started at high); according to Anthropic, Opus 5.5 at medium matches or beats Opus 5 at high in its coding evaluations. The official guide:
low: small changes you review yourself (a rename).medium: daily work with a clear scope.high: when verification matters or there are edge cases (a bug in existing code).xhigh: more reasoning at the cost of tokens.max: hard problems without supervision, with a warning about diminishing returns.
Change it with /effort (with s for this session only), with --effort at startup, per model in modelSettings, or with CLAUDE_CODE_EFFORT_LEVEL, which overrides everything. For a single turn, write ultrathink in the prompt: since 2.1.311 it raises that turn to xhigh. Watch out: an old effortLevel in your ~/.claude/settings.json is not applied to Opus 5.5.
How we tested it (methodology)
1. A single realistic, verifiable task
A 2-file Python repository where the model has to: (1) fix a test that fails because round() uses banker's rounding and the business wants ROUND_HALF_UP, (2) add apply_coupon() with three rules and (3) write its tests. The prompt is identical for every model (en/TASK.md; the Spanish original, TAREA.md, is in the repo root):
You are in a small Python project (src/ and tests/ folders).
1. Run `python3 -m pytest -q` and fix the failure in `tests/test_prices.py` without touching the tests.
2. Add to `src/prices.py` the function `apply_coupon(total, code)` with these rules:
- "WELCOME" takes 10 % off; "SHIPPING" takes 4.99 off if the total is at least 30; any other code raises ValueError.
- The result never goes below 0 and is rounded to 2 decimals with ROUND_HALF_UP from the decimal module (not with round()).
3. Write tests for `apply_coupon` in `tests/test_coupon.py`.
4. Finish when `python3 -m pytest -q` passes completely. Don't install packages or use the network.
The starting code has the bug on purpose. en/template/src/prices.py:
from decimal import Decimal
def total_with_vat(net, vat=0.21):
return round(net * (1 + vat), 2)
And the visible test that fails, en/template/tests/test_prices.py:
from src.prices import total_with_vat
def test_vat_rounds_half_up():
# 10.125 € net at 0 % VAT must end up as 10.13 (ROUND_HALF_UP), not 10.12
assert total_with_vat(10.125, 0) == 10.13
def test_normal_vat():
assert total_with_vat(100, 0.21) == 121.0
2. Quality is decided by hidden tests, not by our opinion
When the run ends, the script copies a test file the model has never seen (7 cases, including edges such as 29.99 € or 0.045) and counts how many pass. en/hidden/test_hidden.py:
import pytest
from src.prices import apply_coupon, total_with_vat
def test_welcome(): assert apply_coupon(50, "WELCOME") == 45.0
def test_shipping_minimum(): assert apply_coupon(30, "SHIPPING") == 25.01
def test_shipping_below(): assert apply_coupon(29.99, "SHIPPING") == 29.99
def test_never_negative(): assert apply_coupon(0, "WELCOME") == 0
def test_round_half_up(): assert apply_coupon(0.05, "WELCOME") == 0.05 # 0.045 -> 0.05
def test_invalid_code():
with pytest.raises(ValueError): apply_coupon(10, "FAKE")
def test_vat_still_right(): assert total_with_vat(2.675, 0) == 2.68
Before publishing we checked the tests themselves: a reference solution passes all 9 tests (2 visible + 7 hidden) and the starting code fails the visible test, as intended. Look at total_with_vat(2.675, 0): the float 2.675 is really 2.67499…, so only a solution that converts with Decimal(str(x)) passes.
3. What we measure and under which conditions
From the JSON output of claude -p we take total_cost_usd, duration_ms and num_turns, and with git we count the lines added in src/. Matrix: 3 models × 2 efforts (medium, high) × 3 repetitions = 18 runs; Fable 5.1 is added only if approved separately (6 more).
Each run starts in a fresh temporary folder, with --bare so no hooks, skills or CLAUDE.md from the machine are loaded (comparable results), --permission-mode dontAsk with a closed list of tools, no network, and caps of --max-budget-usd 2 and --max-turns 40. Versions are pinned: Claude Code 2.1.316 and IDs claude-opus-5-5, claude-sonnet-5-5 and claude-haiku-5-5.
4. How much it costs before running it
Assumption per run: about 200 K input tokens processed in total (adding up turns) and 8 K output tokens.
| Model | Upper bound per run (no cache) | 6 runs |
|---|---|---|
| Opus 5.5 | ≈ 0.96 USD | 5.76 USD |
| Sonnet 5.5 | ≈ 0.48 USD | 2.88 USD |
| Haiku 5.5 | ≈ 0.02 USD | 0.14 USD |
| Fable 5.1 (optional) | ≈ 2.40 USD | 14.40 USD |
Upper-bound total: 8.8 USD without Fable and 23.2 USD with Fable. Realistic estimate, with ~85 % of the input read from cache at the official read prices: about 3 USD without Fable and 7.5 USD with Fable (it does not include the cache write surcharge, which we have not verified). The --max-budget-usd 2 cap limits the worst case to 36 USD (18 runs) or 48 USD (24).
The plan: phase 1 smoke test with 3 runs (≈ 0.5-1.5 USD) to check the script works; if it goes well, full phase 2 without Fable; Fable is a separate decision. With a subscription instead of an API key, --bare does not work (it does not read the login), the cost figures are estimates and what you spend is plan limit.
5. Run it yourself
Requirements: Claude Code 2.1.316, git, python3 with pytest, jq and an ANTHROPIC_API_KEY (needed for --bare and for an exact cost). en/measure.sh does one run and appends a line to en/results.jsonl:
#!/usr/bin/env bash
# measure.sh — one run of the test: ./measure.sh <model> <effort> <repetition>
# Requires: claude 2.1.316, git, python3 with pytest, jq. Recommended mode: --bare with ANTHROPIC_API_KEY.
# Costs money: each run is capped at 2 USD (--max-budget-usd 2).
set -euo pipefail
[ $# -eq 3 ] || { echo "Usage: $0 <model> <effort> <repetition>" >&2; exit 2; }
model="$1"; effort="$2"; rep="$3"
root="$(cd "$(dirname "$0")" && pwd)"
dir="$(mktemp -d /tmp/model-test.XXXXXX)"
cp -r "$root/template/." "$dir/"
cd "$dir"
git init -q && git add -A && git -c user.name=test -c user.email=test@local commit -qm base
start=$(date +%s)
claude --bare -p "$(cat "$root/TASK.md")" \
--model "$model" --effort "$effort" \
--permission-mode dontAsk \
--allowedTools "Read" "Edit" "Bash(python3 -m pytest *)" \
--max-budget-usd 2 --max-turns 40 \
--no-session-persistence \
--output-format json > result.json || true
end=$(date +%s)
[ -s result.json ] || echo '{"is_error": true}' > result.json # if claude wrote nothing, the row is marked as an error
# Quality: hidden tests the model hasn't seen
cp "$root/hidden/test_hidden.py" tests/
if python3 -m pytest -q tests/test_hidden.py > hidden.txt 2>&1; then hidden=ok; else hidden=fail; fi
passed=$(grep -Eo '[0-9]+ passed' hidden.txt | grep -Eo '[0-9]+' || echo 0)
lines=$(git diff --shortstat HEAD -- src | grep -Eo '[0-9]+ insertion' | grep -Eo '[0-9]+' || echo 0)
jq -c --arg m "$model" --arg e "$effort" --arg r "$rep" --arg o "$hidden" \
--arg p "$passed" --arg l "$lines" --arg s "$((end-start))" \
'{model:$m, effort:$e, rep:($r|tonumber), cost_usd:.total_cost_usd,
ms:.duration_ms, ms_api:.duration_api_ms, turns:.num_turns, error:.is_error,
hidden:$o, hidden_passed:($p|tonumber), src_lines:($l|tonumber), wall_seconds:($s|tonumber),
per_model:.modelUsage}' result.json | tee -a "$root/results.jsonl"
echo "Run folder: $dir"
en/batch.sh launches the smoke phase or the full matrix and prints the summary:
#!/usr/bin/env bash
# batch.sh — phase 1 (smoke, 3 runs, ~0.5-1.5 USD): ./batch.sh smoke · phase 2 (18 runs): ./batch.sh full
# Costs money. Check results.jsonl after the smoke phase before launching the full one.
set -euo pipefail
cd "$(dirname "$0")"
models="claude-haiku-5-5 claude-sonnet-5-5 claude-opus-5-5" # Fable 5.1 only if approved separately: add "fable"
case "${1:-smoke}" in
smoke) for m in $models; do ./measure.sh "$m" medium 1; done ;;
full) for rep in 1 2 3; do for e in medium high; do for m in $models; do ./measure.sh "$m" "$e" "$rep"; done; done; done ;;
*) echo "Usage: $0 [smoke|full]" >&2; exit 2 ;;
esac
# Summary per model and effort: mean cost and seconds, median of hidden tests passed (out of 7)
jq -s 'group_by(.model+"/"+.effort) | map({key:(.[0].model+"/"+.[0].effort),
n:length, mean_cost:(map(.cost_usd // 0)|add/length), mean_seconds:(map(.ms // 0)|add/length/1000),
hidden_median:(map(.hidden_passed)|sort|.[length/2|floor]),
hidden_ok:(map(select(.hidden=="ok"))|length), mean_turns:(map(.turns // 0)|add/length)})' results.jsonl
The steps:
git clone https://github.com/carlosp5751258-lgtm/ctrlia-opus-sonnet-haiku
cd ctrlia-opus-sonnet-haiku/en
export ANTHROPIC_API_KEY=... # your own; do not save it in the repository
./batch.sh smoke # 3 runs; check results.jsonl
./batch.sh full # only if the smoke phase went well
Comparison table
List prices per million tokens, according to the official CHANGELOG:
| Model | Alias (Anthropic API) | Since | Input / output (USD) | Cache read (USD) | Default effort |
|---|---|---|---|---|---|
| Fable 5.1 | fable | 2.1.257 | 10 / 50 | 0.25 | no data |
| Opus 5.5 | opus | 2.1.280 | 4 / 20 | 0.20 | medium |
| Sonnet 5.5 | sonnet | 2.1.284 | 2 / 10 | 0.10 (down from 0.20) | medium |
| Haiku 5.5 | haiku | 2.1.293 | 0.10 / 0.50 (0.50 / 2.50 if the prompt exceeds 100 K tokens) | no data | medium |
Test results: pending. This is the table that will be published, one row per model and effort:
| Model / effort | Average cost per run | Average seconds | Turns | Hidden tests (out of 7, median of 3) | Runs fully green | Lines added in src/ |
|---|---|---|---|---|---|---|
| Haiku 5.5 / medium | pending | pending | pending | pending | pending | pending |
| Haiku 5.5 / high | pending | pending | pending | pending | pending | pending |
| Sonnet 5.5 / medium | pending | pending | pending | pending | pending | pending |
| Sonnet 5.5 / high | pending | pending | pending | pending | pending | pending |
| Opus 5.5 / medium | pending | pending | pending | pending | pending | pending |
| Opus 5.5 / high | pending | pending | pending | pending | pending | pending |
Rules for reading it without fooling yourself:
- It is a small task: do not extrapolate to large refactors, where the documentation gives Fable the edge.
- We will show the spread, not just the average.
total_cost_usdis a client-side estimate and may differ from the invoice.- Date and version in the header; we will repeat the test with every new model.
When to use each one
Based on the official documentation (it will be adjusted with the test data):
| Task | Model | Effort |
|---|---|---|
| Renames, explaining a file, high-volume tasks | Haiku 5.5 | low or medium |
| Search or summary subagents | Haiku 5.5 (model: haiku in the subagent frontmatter) | low |
| Day-to-day work with a clear scope | Sonnet 5.5 | medium |
| Bugs in existing code, design decisions, when Sonnet gets it wrong | Opus 5.5 | medium (default) or high |
| Work bigger than one session, root cause, architecture | Fable 5.1 (describe the outcome, not the steps) | depends on the task |
| Plan first, then implement | opusplan (Opus in plan mode, Sonnet when executing) | medium |
Three more tips:
opusplanis exactly what you want if you are thinking "plan in Opus, implement in Sonnet".- So subagents do not inherit an expensive model, set
modelin their definition (see the subagents recipe) or useCLAUDE_CODE_SUBAGENT_EFFORT(2.1.316). - Switching models mid-session forces a turn without cache, so decide at the start.
The commands, verified in the documentation:
claude --model sonnet --effort medium # this session only
/model haiku # inside the session (Enter = save as default; s = this session only)
/effort high # or /effort with no arguments for the slider
/model opusplan # Opus in plan mode, Sonnet when executing
export CLAUDE_CODE_EFFORT_LEVEL=low # overrides everything else
export CLAUDE_CODE_SUBAGENT_EFFORT=low # effort for subagents that do not set one (2.1.316)
/status # see the current model
And a default model with per-model effort in ~/.claude/settings.json:
{
"model": "sonnet",
"modelSettings": {
"claude-opus-5-5": { "effortLevel": "high" }
}
}
If you want a skill that always runs on Haiku even when your session is on Opus, look at /explica in the 5-command pack. To see what you pay with each plan, read the Claude Code pricing recipe. Sources: model configuration, headless mode, CLI reference, costs, cost tracking, prompt caching and the CHANGELOG.
FAQ
Which model does Claude Code use by default?
With the default value, Opus 5.5 on Pro, Max, Ultra, Team, Enterprise and the Anthropic API (since 2.1.280). On Microsoft Foundry, Sonnet 4.5. Fable is never the default. Check it with /status.
Opus or Sonnet for coding?
The documentation describes Sonnet for daily work and Opus for complex reasoning. Sonnet 5.5 costs half as much (2/10 USD per million versus 4/20). Start with Sonnet at medium and move up to Opus when it fails on bugs in existing code or on design; our test will show how much it changes on a specific task.
How do I plan with Opus and implement with Sonnet?
With /model opusplan: it uses Opus in plan mode (Shift+Tab or /plan) and switches to Sonnet only when executing. With /model opusplan[1m] you ask for 1M context in both phases where it is not the default.
Which effort should I set?
Leave medium (the default for the 5.5 models) for normal work, high for bugs with edge cases and low for small changes you review. max only for hard problems: the documentation warns it can overthink. For a single hard turn, write ultrathink.
Is Haiku 5.5 good for anything serious?
For simple tasks and for search or summary subagents it is the cheap option (0.10/0.50 USD per million, 1M context). Watch out: if the prompt exceeds 100 K tokens the price goes up to 0.50/2.50.
Why doesn't Fable show up, or why does it ask for credits?
Depending on your plan, Fable may be billed with usage credits instead of the included limit; the picker marks it with "Requires usage credits". It requires 2.1.257 or later.
Does switching models mid-session cost more?
Yes: the caching documentation explains that switching models causes a turn without cache. Choose the model at the start, or use opusplan, which is designed for that switch.
When will you publish the results?
When the test is run, which costs money and needs approval. Until then the page only has the methodology and the script so anyone can repeat it; we will never publish figures that do not come from the results file (results.jsonl, or resultados.jsonl in the Spanish version).