I opened /model to pick the third row, and pressed the one below it. Again.
There are two or three rows I actually use. The rest sit above them, and my fingers keep landing where the row used to be rather than where it is.
Claude Code v2.1.242 added modelPicker, a setting that lets you write that lineup yourself. I assumed it was cosmetic. Writing one turned out to involve a few decisions that are not obvious from the key name — what disappears when you replace the built-in rows, and which settings file actually gets read.
The default lineup is built for everyone, which makes it long for one person
Here is what the built-in aliases point at.
| Alias | What it selects |
|---|---|
default | A special value that clears any model override and reverts to your account's runtime default. Not an alias to a model |
best | Fable 5 where your organization has access to it, otherwise the latest Opus |
fable | Claude Fable 5 |
opus | The latest Opus |
sonnet | The latest Sonnet |
haiku | The fast, efficient Haiku for simple tasks |
opus[1m] / sonnet[1m] | The 1 million token context window variants |
opusplan | opus during plan mode, then sonnet for execution |
What those aliases resolve to also depends on where you are connected.
| Provider | opus | sonnet |
|---|---|---|
| Anthropic API | Opus 5 | Sonnet 5 |
| Claude Platform on AWS | Opus 5 | Sonnet 4.6 |
| Amazon Bedrock / Google Cloud's Agent Platform | Opus 5 | Sonnet 4.5 |
| Microsoft Foundry | Opus 4.6 | Sonnet 4.5 |
That coverage is a good default. But when you are an indie developer running a handful of projects on your own, the rows you reach for during the day and the ones you hand long jobs to at night settle into a small, stable set. My hesitation was never about which model to pick. It was about which position that model happened to occupy today.
The smallest useful modelPicker
modelPicker is an object with an options array and an optional replaceBuiltInOptions boolean. Each row requires model; label and description are optional.
{
"modelPicker": {
"options": [
{ "model": "opus", "label": "Design and investigation" },
{
"model": "sonnet",
"label": "Daytime implementation",
"description": "Enough for most of what I do"
},
{ "model": "haiku", "label": "Formatting and lookups" }
]
}
}Each row's model is taken verbatim, so it accepts anything --model accepts: an alias like opus, an Anthropic model ID, or a provider-format ID for Amazon Bedrock, Google Cloud's Agent Platform, Microsoft Foundry, or an LLM gateway.
Leave out label and Claude Code titles the row with its built-in name for a model it recognizes, or with the model ID otherwise. Leave out description and it writes a generic second line.
One point worth stating plainly: a label changes what the picker shows, not which model runs. A row you name "production" does not become a production model — the ID in model is what gets used. It sounds obvious, but while you are arranging labels it is easy to lose track of which name is sitting on which ID. Opening /model right after you save is a cheap way to keep them aligned.
Appending versus replacing
replaceBuiltInOptions defaults to false. In that state your rows are added after the built-in lineup, and Claude Code skips any listed model the built-in lineup already covers.
Set it to true and the picker shows only your rows, plus Default, plus a row for the model the session is already using. Here is what goes away.
| Where the row comes from | replaceBuiltInOptions: false | replaceBuiltInOptions: true |
|---|---|---|
| The built-in lineup | Shown first | Hidden |
Rows added for availableModels entries | Shown | Hidden |
| Models found by gateway discovery | Shown | Hidden |
ANTHROPIC_CUSTOM_MODEL_OPTION | Shown | Hidden |
Default and the current model | Shown | Shown |
On my own machine I run with true. A three-row picker helped more than I expected it to. In an environment with an internal gateway or Bedrock in the mix, though, I would start at false. Turning it on hides the rows gateway discovery found, and the person who discovers that is usually not the person who wrote the setting.
The same applies to ANTHROPIC_CUSTOM_MODEL_OPTION. That single custom entry sits after the built-in rows, and any modelPicker rows you append come after it. Switch to true and both disappear together.
Project settings are ignored on purpose
This is where my instincts were wrong.
Claude Code reads modelPicker from managed settings, --settings, and user settings. It ignores the key in project and local settings, so a repository you clone cannot relabel your picker.
Beyond that, the highest-precedence of those three supplies the entire lineup. Claude Code never combines lineups from two sources. Three rows in user settings and two in managed settings does not give you five — it gives you the two from managed settings.
If you are used to committing .claude/settings.json and sharing it with a team, expect to trip here once. I put the key in project settings, opened /model, saw nothing change, and started doubting my JSON. The JSON was fine. The file was the problem.
There is a comparable "written here, applies there" gap around default models. Adding ANTHROPIC_DEFAULT_MODEL Changes Nothing While ANTHROPIC_MODEL Is Still Set covers that one.
Layering it over availableModels can narrow your choices
Writing a modelPicker does not exempt you from availableModels. The allowlist still applies to your rows, and a row outside it will not appear.
The trap is in how you add entries to that allowlist. An entry naming a specific model in a family disables that family's wildcard. Using the documented example, ["sonnet", "claude-sonnet-4-5"] allows only Sonnet 4.5 versions — not every Sonnet.
That interacts badly with a very natural workflow: you pin specific production model IDs in your picker, then add those same IDs to the allowlist so the rows survive. The wildcard you thought was keeping the whole family available quietly stops doing so.
Reversing the order avoids it:
- Decide in
availableModelswhat should remain selectable - Check whether you have mixed wildcards and specific IDs in the same family
- Then write the
modelPickerlineup - Open
/modeland count the rows against what you expected
Step 4 is the one you will want to skip. The picker does not announce what is missing, so counting is the fastest check I found.
Reading a row that never shows up
When a row you wrote does not appear, Claude Code has checked it against the session and taken one of three paths.
| Outcome | What it means |
|---|---|
| Dropped | A row Claude Code cannot serve — a retired model, or one your organization has no access to |
| Grayed out | A row you cannot select yet, shown with the reason, and moved to the bottom of the list |
| No row survives | Claude Code keeps the built-in lineup, filtered by the allowlist as usual |
A row it cannot parse is dropped on its own, and the rest are kept. Your settings file does not fail wholesale, so when exactly one row is missing, suspect that row's syntax first.
Grayed-out rows are not removed; they move to the bottom. If the order looks wrong, scroll down before you edit anything — the reason is printed right there.
Where to start
Write a single row and open /model. Leave replaceBuiltInOptions alone and add something like { "model": "sonnet", "label": "Daytime implementation" }. Your label appears at the end of the built-in lineup, and from there you can decide whether replacing the rest is worth it.
Once the lineup settles, the next thing that starts to matter is what you keep re-sending to those models. I wrote up how I decide the cache lifetime after stepping away in I decide the prompt cache TTL by where I come back to, not how long I was away.
It is a small setting. Having the screen you open every day match the way you actually work turned out to be a better trade than I expected.