Chapter 3. Claude Code Models, Reasoning Effort, and the Context Window
Claude Code takes a model as an alias or a full name, and nine
aliases exist. Resolution depends on your plan, your provider, and your
binary version: default gives Opus 5 on Max and Sonnet 5 on
Pro, best selects Fable 5 where the org has access,
opusplan runs opus in plan mode and
sonnet for execution, and [1m] picks the 1M
context window.
The binary on my Mac reports 2.1.211. That number decides which
models I can select before any account tier or provider question enters
the picture: the docs put Opus 5 selection at v2.1.219 or later, so the
newest Opus does not exist for this machine until I update it. The pin
is deliberate. claude doctor reports auto-updates disabled
by the DISABLE_AUTOUPDATER env var, with the last update
attempt succeeding to 2.1.211 on 2026-07-16. A pinned binary is a stable
binary, and the price of stability is that documentation and installed
behavior drift apart. Run claude --version before you trust
any model claim against your own install, and treat every version gate
below as part of the fact it gates.
The nine aliases,
and what default resolves to
Claude Code exposes nine model aliases: default,
best, fable, sonnet,
opus, haiku, sonnet[1m],
opus[1m], and opusplan. Two of them do more
than point at a model.
default is a reset value rather than an alias. It hands
selection back to whatever your account type resolves to, and that
resolution splits by plan and provider:
| Account or provider | default resolves to |
|---|---|
| Max, Team Premium, Enterprise pay-as-you-go | Opus 5 |
| Anthropic API, Claude Platform on AWS, Amazon Bedrock, Google Cloud’s Agent Platform | Opus 5 |
| Pro, Team Standard, Enterprise subscription seats | Sonnet 5 |
| Microsoft Foundry | Sonnet 4.5 |
best selects Fable 5 where the org has access and
otherwise the latest Opus. opusplan is a compound: it uses
opus during plan mode and switches to sonnet
for execution, which buys the expensive model’s judgment for the
decisions and the cheaper model’s throughput for the typing.
opusplan[1m] forces the 1M context window for both
phases.
On 2.1.211, claude --model --help prose names
fable, opus, and sonnet as
aliases and gives claude-fable-5 as the example of a full
model name. Full names work anywhere an alias does.
Why
opus and sonnet mean different models on
different providers
An alias is a request, and the provider interprets it. The same word lands on different models depending on where your traffic goes:
| Provider | opus |
sonnet |
|---|---|---|
| Anthropic API | Opus 5 | Sonnet 5 |
| Claude Platform on AWS | Opus 5 | Sonnet 4.6 |
| Amazon Bedrock | Opus 5 | Sonnet 4.5 |
| Google Cloud’s Agent Platform | Opus 5 | Sonnet 4.5 |
| Microsoft Foundry | Opus 4.6 | Sonnet 4.5 |
Layer the binary version on top of that. Minimum Claude Code versions
by model: Opus 5 requires v2.1.219 or later, Sonnet 5 requires v2.1.197,
Opus 4.8 requires v2.1.154, Fable 5 requires v2.1.170. A script that
says --model opus means one thing on my laptop and a
different thing in a Bedrock-backed CI job, and neither of those is a
bug. Write the full model name into anything that has to behave the same
in two places.
Four ways to set the model, and which one wins
Four mechanisms select the model, and their precedence is fixed:
/model in the session wins, then --model at
startup, then the ANTHROPIC_MODEL env var, then the
model field in settings.
# startup flag
claude --model fable
# environment
ANTHROPIC_MODEL=claude-fable-5 claude
# in session, highest precedence
/model opus[1m]The in-session command reaches beyond the interactive terminal. In
-p (print) mode, /model, /effort,
/fast, /color, and /rename accept
their value as an argument, as in /model sonnet, from
v2.1.205, and /config key=value works from v2.1.181;
terminal-only commands such as /login stay unavailable
there. The same argument form for /model and
/effort works from mobile and web Remote Control. The
background-agent surface carries the flag too: on 2.1.211,
claude agents --help lists
--model <model> alongside
--effort <level>.
A second family of env vars remaps what the aliases mean rather than
picking a model: ANTHROPIC_DEFAULT_FABLE_MODEL,
ANTHROPIC_DEFAULT_OPUS_MODEL,
ANTHROPIC_DEFAULT_SONNET_MODEL, and
ANTHROPIC_DEFAULT_HAIKU_MODEL. The old
ANTHROPIC_SMALL_FAST_MODEL is deprecated in favor of the
haiku variable. CLAUDE_CODE_SUBAGENT_MODEL pins the model
for subagents; it accepts an alias or a full name, overrides both
per-invocation model arguments and subagent frontmatter,
and setting it to inherit restores normal resolution. That
override strength cuts both ways: a forgotten export in a shared shell
profile flattens the per-subagent models a repo chose on purpose.
For gateways and pinned deployments there are picker-shaping
variables: ANTHROPIC_CUSTOM_MODEL_OPTION adds one custom
entry to the /model picker, with
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME and
ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION optional (the
description defaults to Custom model (<model-id>)).
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 populates the
picker from a gateway. When a pinned model needs its capabilities
declared,
ANTHROPIC_DEFAULT_<FAMILY>_MODEL_SUPPORTED_CAPABILITIES
takes a comma-separated list drawn from effort,
xhigh_effort (v2.1.111 or later), max_effort,
thinking, adaptive_thinking, and
interleaved_thinking.
Governance lives in settings keys: availableModels is an
allowlist matching a family, a version prefix, or a full ID;
enforceAvailableModels extends that allowlist to the
Default option and requires v2.1.175; modelOverrides maps
Anthropic model IDs to provider-specific strings; model and
fallbackModel round out the set.
Prompt caching is switchable per model family through the same
env-var pattern. DISABLE_PROMPT_CACHING turns it off for
all models and takes precedence;
DISABLE_PROMPT_CACHING_HAIKU,
DISABLE_PROMPT_CACHING_SONNET,
DISABLE_PROMPT_CACHING_OPUS, and
DISABLE_PROMPT_CACHING_FABLE, each set to 1,
scope the switch to one family. I have no reason to run with caching off
in normal work; the per-family variables exist for the case where one
family’s caching behavior needs isolating, and knowing the exact names
beats rediscovering them mid-incident.
Fallback
chains: --fallback-model and the classifier reroute
--fallback-model takes a comma-separated list, as in
claude --fallback-model sonnet,haiku, and the persistent
equivalent is the fallbackModel settings key as an array.
Chains are capped at three models after duplicate removal, the literal
string "default" expands to the default model, and a
fallback switch lasts for the current turn only. The 2.1.211 help text
adds that the primary is retried at the start of each user turn and that
the flag works only with --print. That last claim is
documentation, not enforcement: I ran
claude --fallback-model sonnet hi without
--print on this machine and it fell through to normal
startup instead of erroring. The stream-json flag family hard-errors on
bad combinations; the print-only trio (--fallback-model,
--max-budget-usd, --no-session-persistence)
does not.
There is also a fallback you do not choose. From v2.1.219, a content classifier can reroute a request by category: on Fable 5, biology-flagged requests re-run on Opus 5 and cybersecurity-flagged requests re-run on Opus 4.8; on Opus 5, cybersecurity-flagged requests re-run on Opus 4.8 while biology-flagged requests end in refusal. In non-interactive mode a flagged request ends the turn with a refusal instead of prompting. If you run headless pipelines, that is a terminal state your error handling has to expect, not a dialog someone will click through.
Which effort levels each model supports
Effort is a per-model capability, not a universal dial:
| Model | Supported effort levels |
|---|---|
| Fable 5, Opus 5, Sonnet 5, Opus 4.8, Opus 4.7 | low, medium, high,
xhigh, max |
| Opus 4.6, Sonnet 4.6 | low, medium, high,
max |
| Models not listed | no effort support |
The default is high on each model that supports effort,
with one exception: Opus 4.7 defaults to xhigh. An
unsupported level falls back to the highest supported level at or below
it, so xhigh runs as high on Opus 4.6 without
complaint. That fallback is convenient in a terminal and a trap in a
fleet: the level you wrote and the level you got can differ by model,
and nothing fails.
Six ways to set effort, and which one wins
Six mechanisms set effort: /effort <level> in
session, /effort auto, the --effort flag at
startup, the CLAUDE_CODE_EFFORT_LEVEL env var, the
effortLevel settings key, and effort
frontmatter in a skill or subagent. The env var takes precedence over
all other methods, which inverts the usual expectation that the flag on
the command line is the last word. A
CLAUDE_CODE_EFFORT_LEVEL exported in a CI environment beats
the --effort you pass in the job’s own command. When an
automation run behaves as if it ignored your flag, check the environment
before you check the flag.
In -p mode, /effort accepts its value as an
argument from v2.1.205, same as /model, and the same form
works from mobile and web Remote Control.
Validation on the flag is soft, and I confirmed the shape of it on
2.1.211: claude --effort nonsense -p hi does not exit with
an error. It prints a warning naming the unknown value, says it is
ignoring it and using the default effort, and lists the valid values as
low, medium, high, xhigh, max. Compare
--output-format, which rejects a bad value with a hard
error and an allowed-choices list. The soft path means a typo in a cron
job’s effort flag runs the whole job at default effort behind a warning
line nobody reads. Grep your automation logs for
Unknown --effort value once; it costs nothing.
max, ultracode, and ultrathink
max and ultracode are session-only. Neither
is accepted in the effortLevel settings key or in
CLAUDE_CODE_EFFORT_LEVEL; the four persistent levels are
low, medium, high, and
xhigh. You cannot make maximum effort your standing
default, and I read that as intentional friction.
ultracode is a Claude Code setting, not a model effort
level. It sends xhigh to the model and layers dynamic
workflow orchestration on top, on the harness side. Three switches
enable it: /effort ultracode in session,
claude --effort ultracode at startup, or
"ultracode": true inside --settings JSON. The
flag form requires v2.1.203; earlier binaries treated
ultracode as an unknown effort value and warned.
ultrathink is a different mechanism again. The literal
keyword anywhere in a prompt adds an in-context instruction requesting
deeper reasoning for that turn; the effort level sent to the API does
not change. Phrases like “think”, “think hard”, and “think more” are not
recognized keywords and pass through as ordinary prompt text. One magic
word, no synonyms.
How I pick an effort level
I pick effort by the consequence of being wrong, not by how
interesting the problem is. A rename with a test suite behind it can run
at low or medium: a wrong answer trips the
suite and costs one re-run. high is the shipped default and
the right call for most feature work. A schema migration, an auth
change, anything whose last step is a production deploy gets
xhigh or max, because the failure mode there
is not a red test, it is an incident with my name on it. The pattern I
catch myself in, and correct, is the inverted one: burning
max on a question I find interesting and letting an
irreversible change ride at the default. Curiosity reads the same at any
effort level. Blast radius is what the dial is for.
The same stance picks models. opusplan encodes it at the
alias level: the plan, where a wrong call compounds through everything
built on it, gets opus; the execution, where each step is
checkable against the plan, gets sonnet. Spend where the
decision is, not where the typing is.
Extended thinking and its own controls
Extended thinking has its own controls, independent of effort.
Option+T on macOS (Alt+T on Windows and Linux)
toggles thinking for the session. /config sets the global
default, persisted as alwaysThinkingEnabled in
~/.claude/settings.json. MAX_THINKING_TOKENS=0
disables thinking on the Anthropic API, except on Fable 5, where it does
not. showThinkingSummaries: true surfaces full summaries,
and Ctrl+O toggles the verbose display.
CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1 reverts to fixed
thinking budgets and applies only to Opus 4.6 and Sonnet 4.6.
Two more controls exist on 2.1.211 that claude --help
does not list. The parser accepts --thinking <mode>
with the choice-validated values enabled,
adaptive, and disabled, and
--max-thinking-tokens <tokens> with a numeric
argument. I found both by probing: unknown options error with
unknown option, so a flag the parser accepts exists whether
or not the help text admits it.
The 1M context window and which models have it
The 1M-token context window is supported by Fable 5, Sonnet 5, Opus
4.6 and later, and Sonnet 4.6. You select it with the [1m]
suffix on an alias or a full model name: /model opus[1m],
or claude-opus-4-8[1m] spelled out.
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 removes the 1M variants
from the picker, which is the lever if long-context cost is a budget
problem on a team.
Sonnet 5 on the Anthropic API is the special case: it runs with the
1M window as its only configuration. There is no 200K variant and no
[1m] suffix for it. Sessions auto-compact at about 967K
tokens by default, and CLAUDE_CODE_AUTO_COMPACT_WINDOW
moves that threshold. The gap between 967K and the window ceiling is
headroom: the compactor fires before the wall, not at it.
The Claude Code docs carry the 1M boundary and the 967K threshold;
per-model default window sizes live on the platform documentation
instead. I am not reproducing a per-model table here, because I did not
verify one. What the Claude Code docs do state is the boundary: Fable 5,
Sonnet 5, Opus 4.6 and later, and Sonnet 4.6 support the 1M window, and
you select it with the [1m] suffix on an alias or a full
model name.
modelUsage:
the receipt for which model ran
The receipt is in the output: with --output-format json,
the payload includes a modelUsage field reporting the
actual model used, alongside total_cost_usd, a per-model
cost breakdown, and the session_id. Aliases resolve by
provider, versions gate what a binary can select, an env var can outrank
your flag, and a fallback chain can swap models for a turn. After all of
that, the model you asked for is a request. In automation I log
modelUsage next to the model I requested, and the two
disagree often enough to justify the line. When they differ, the
explanation is in this chapter’s tables, and the tables beat my memory
of them.