The morning after the update, the first line of /status showed a model I had never picked: Opus 5.5. I hadn't edited anything the night before, and when I opened .claude/settings.json the model line was still empty.
The repository I use for maintaining my wallpaper apps has a handful of small agents under .claude/agents/. One keeps the store listings aligned across languages, one collects Kotlin warnings, and one tidies up draft replies to reviews. Several of them were written with model: "opus", an alias, and until that morning I had never given it a second thought.
What I'd like to say first is that I didn't change a single line of configuration that week. Before changing anything, I wanted to count what had actually carried over and what hadn't.
Reading what changed before touching any setting
Here is the part I confirmed against the changelog text. In 2.1.280, claude-opus-5-5 was added and became the default Opus model: 1M context, $4 input and $20 output per Mtok, and cache reads at $0.20 per Mtok. In the same release, the default model for Pro and Team Standard moved from Sonnet to Opus.
So my /status that morning was not a bug. Pro's default had leaned toward Opus, and the opus alias now pointed at 5.5. Those two steps stacked, and I found myself standing on 5.5 without having chosen it.
| Item | Before 2.1.280 | From 2.1.280 |
|---|---|---|
| Pro default model | Sonnet | Opus |
What the opus alias resolves to | The default Opus at the time | Opus 5.5 (claude-opus-5-5) |
| Opus 5.5 pricing | — | $4 in / $20 out / $0.20 cache read (per Mtok) |
A saved /effort | Kept per model | Not applied to new models |
/status and claude doctor are the right tools for this. Since 2.1.282, environment variables for telemetry that are being ignored also show up at startup and in /status, so "I set it but it isn't doing anything" can be spotted on the same screen.
On a morning when the default has moved, read what is actually in effect on one screen before you fix anything. That ordering is the one thing I try not to skip, even on a rushed day.
Reading where the alias actually points
Which model were my alias-based agents resolving to after the update? I decided to answer that from a run on my own machine rather than from a list. There was an issue report describing an agent with model: "opus" picking an older generation. Whether that matches your environment depends on the environment, but it was clearly worth checking.
The first step is to list every model: value scattered across the agent definitions and separate fixed IDs from aliases. It's a short shell script.
#!/usr/bin/env bash
# Pull model: from the frontmatter of .claude/agents/*.md and sort aliases from fixed IDs
set -euo pipefail
DIR="${1:-.claude/agents}"
for f in "$DIR"/*.md; do
m=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm==1 && /^model:/{sub(/^model:[ \t]*/,""); gsub(/["'"'"']/,""); print; exit}' "$f")
case "${m:-inherit}" in
opus|sonnet|haiku|inherit) kind="alias" ;;
claude-*) kind="fixed" ;;
*) kind="other" ;;
esac
printf '%-6s %-24s %s\n' "$kind" "${m:-inherit}" "$(basename "$f")"
done | sortOn my machine, three of six agents said opus, one said inherit, and two used fixed IDs. Until I counted, I had no idea I'd leaned that far toward aliases.
Next, run the alias once and read which ID it actually resolved to. The quickest way is a headless call with a tiny prompt, then reading the keys under modelUsage in the result JSON.
claude -p "reply with ok" --model opus --output-format json \
| python3 -c 'import json,sys; r=json.load(sys.stdin); print(list(r.get("modelUsage",{}).keys()))'If claude-opus-5-5 appears, the alias points at the new default. If a different ID appears, you have a decision to make about trusting aliases in that repository. The reason I don't stop at the list is that alias resolution is decided by the CLI version at run time, and nothing about the result is ever written back into the agent file.
A saved /effort does not carry over
There was one more thing I had assumed would carry over: the saved /effort value. The 2.1.280 notes state plainly that an effort level saved before /effort became per-model is not applied to newer models such as Opus 5.5.
I had lowered the effort one notch for the translation agent some time ago. Not so much to reduce variation in the translations as to keep the responses from running long. The moment the default moved to 5.5, that saved value stopped applying to it.
Checking is easy: type /effort in an interactive session and read the current value. If it isn't in effect, set it once for 5.5. For the behavior where thinking is off and a high effort quietly drops a level, I wrote up what I found in Your Config Asks for effort xhigh With Thinking Off, and It Now Quietly Runs at high.
Redoing the monthly estimate with $0.20 cache reads
On pricing, it wasn't the numbers themselves that stopped me but the ratio. I had always estimated monthly cost with the rule of thumb that a cache read costs a tenth of input. At $4 input that would be $0.40, and the changelog says $0.20. The difference matters most for the agents that carry a long cached preamble.
So I wrote a small script that recomputes the estimate per agent from monthly token volume. Your current prices are passed as arguments; only the Opus 5.5 values are written in, exactly as the changelog states them.
#!/usr/bin/env python3
"""Compare the monthly cost at your current prices against Opus 5.5 for one agent.
Usage: python3 estimate.py <input Mtok> <cache-read share 0-1> <output Mtok> <current input $> <current output $> <current cache-read $>
"""
import sys
OPUS_5_5 = {"input": 4.0, "output": 20.0, "cache_read": 0.20} # per Mtok, from the 2.1.280 changelog text
def monthly(price: dict, in_mtok: float, cached: float, out_mtok: float) -> float:
fresh = in_mtok * (1 - cached) * price["input"]
hit = in_mtok * cached * price["cache_read"]
return fresh + hit + out_mtok * price["output"]
def main() -> None:
if len(sys.argv) != 7:
print(__doc__)
sys.exit(2)
in_mtok, cached, out_mtok = map(float, sys.argv[1:4])
current = dict(zip(("input", "output", "cache_read"), map(float, sys.argv[4:7])))
if not 0 <= cached <= 1:
sys.exit("cache-read share must be between 0 and 1")
now = monthly(current, in_mtok, cached, out_mtok)
new = monthly(OPUS_5_5, in_mtok, cached, out_mtok)
print(f"current : ${now:8.2f}")
print(f"Opus 5.5 : ${new:8.2f} (delta {new - now:+.2f})")
# The cache is cold on the first day after a switch, so show the share-0 figure as well
cold = monthly(OPUS_5_5, in_mtok, 0.0, out_mtok)
print(f"day one : ${cold:8.2f} (cache-read share 0)")
if __name__ == "__main__":
main()I kept the "day one" line for a reason. Prompt caches are separate per model, so the day you switch starts from a cold cache. If you only look at the monthly figure, the first day's bill will give you a knot in your stomach. As an indie developer I budget by the month, but in the week of a switch I read the numbers by the day.
Pin it, or follow along
Once I had counted, I drew three lines.
- The agent that aligns store listings was rewritten to a fixed ID. Keeping the tone of the translations steady from one month to the next matters more to me than whatever the newer model is better at
- The warning collector and the draft tidier stayed on the alias. I always read their output myself, and they're exactly the agents I want to benefit from a better model without ceremony
- The single
inheritagent stayed as it was, since reading the parent session's/statusanswers the question
It isn't that pinning or following is correct. What I've settled on is this: agents whose output a person reads again can follow the default; agents whose output goes straight out the door get pinned. Aliases are convenient, and the price of that convenience is that nothing in the file records when the thing behind the name changed.
For how the environment variables interact with all this, see Adding ANTHROPIC_DEFAULT_MODEL Changes Nothing While ANTHROPIC_MODEL Is Still Set. And for recording model switches during unattended runs with hooks, and stopping only the switches you never agreed to, there's Two hooks that record every model switch and stop the ones you never agreed to. Once this recount is done, that felt like the natural next thing to put in place at Dolice.
Before you start today's work, I'd suggest typing /status once and copying the model name on the first line into your notes. That's where I started, too. Thank you for reading.