You pinned a model, but you suspect a different (cheaper) tier is quietly being served. Stop guessing from the UI — your own session logs record, per turn, the exact model ID that was served. Two copy-paste commands turn that into a timestamped timeline and a per-model tally. Everything runs locally against files already on your disk.
Claude Code writes one JSON object per line into a session log:
~/.claude/projects/<project>/<session-id>.jsonl
For every assistant turn, the line has type":"assistant", and inside it message.model is the model ID that was actually served for that turn. The same line carries a top-level timestamp (UTC, ISO 8601). So message.model — not the UI, not the status line — is the canonical record of what you got.
<synthetic>. Some lines have message.model equal to <synthetic>. Those are injected/synthetic turns (e.g. interface-generated messages), not a model that served you. Counting them would pollute the tally, so both commands below drop them.
Across every session, list each assistant turn's served model next to its timestamp, sorted in time order. Paste as-is:
for f in ~/.claude/projects/*/*.jsonl; do
python3 - "$f" <<'PY'
import sys, json
for line in open(sys.argv[1]):
try: d = json.loads(line)
except: continue
if d.get("type") == "assistant":
m = d.get("message", {}).get("model")
if m and m != "<synthetic>":
print(d.get("timestamp",""), m)
PY
done | sort
Each line is one served turn: 2026-06-07T01:23:45.678Z claude-opus-4-8. If a stretch of turns shows a different (lower) tier than the one you pinned, that's your evidence — with the exact UTC time you can correlate against your activity.
Collapse all of that into a count of turns served by each model — the single figure that makes a report concrete:
cat ~/.claude/projects/*/*.jsonl | python3 -c '
import sys, json, collections
c = collections.Counter()
for line in sys.stdin:
try: d = json.loads(line)
except: continue
if d.get("type")=="assistant":
m=d.get("message",{}).get("model")
if m and m!="<synthetic>": c[m]+=1
for m,n in c.most_common(): print(f"{n:7d} {m}")
'
Output is one row per served model, most-served first — for example a column of turn counts beside claude-opus-4-8, claude-sonnet-4-6, and so on.
An all-time tally can be the opposite of what you're getting right now — which is also why a stats "favorite model" that ignores recency misleads (cf. anthropics/claude-code #65899). On one real machine the two windows fully inverted:
| Window | Top served model | Share |
|---|---|---|
| Last 30 days | claude-opus-4-7 | 80% |
| Last 7 days | claude-opus-4-8 | 73% |
So tally only the recent window. Change DAYS to taste:
python3 - <<'PY'
import json, glob, os, collections, datetime
DAYS = 7
cut = (datetime.date.today() - datetime.timedelta(days=DAYS)).isoformat()
c = collections.Counter()
for f in glob.glob(os.path.expanduser("~/.claude/projects/*/*.jsonl")):
for line in open(f):
try: d = json.loads(line)
except: continue
if d.get("type") == "assistant":
m = d.get("message", {}).get("model"); t = d.get("timestamp", "")
if m and m != "<synthetic>" and t[:10] >= cut:
c[m] += 1
tot = sum(c.values()) or 1
for m, n in c.most_common():
print(f"{n*100//tot:3d}% {n:6d} {m}")
PY
Each row is the recent-window share per model — e.g. 73% 24773 claude-opus-4-8 — so a drifted "favorite" stands out at a glance.
The IDs are explicit, so you can see when a turn crossed into a different tier. Real current examples:
Model ID in message.model | What it is |
|---|---|
claude-opus-4-8 | Opus 4.8 (top tier) |
claude-opus-4-7 | Opus 4.7 (top tier) |
claude-sonnet-4-6 | Sonnet 4.6 (mid tier) |
claude-haiku-4-5-20251001 | Haiku 4.5 (fast/low tier) |
Not every difference is equal. Sort what you find by how much it matters:
4-7 ↔ 4-8) is a weak signal. Both are top tier; a swap between them rarely changes output quality enough to matter, and can happen for ordinary reasons.claude-sonnet-4-6) while you pinned Opus is the sharp evidence. A cross-tier downgrade is what produces a felt quality difference, so that's the case worth raising.And turn it into something reproducible rather than an anecdote:
message.model tells you which model served a turn. It does not, by itself, prove intent or that anyone "downgraded you on purpose." Report the observed fact (served model per turn, with times); leave the cause to the people who can audit the routing.
npx cc-safe-setup) · 日本語版
This page describes a local verification method against your own session logs; it is not account or legal advice and makes no claim about Anthropic's routing intent. For model and Usage Policy questions, confirm against Anthropic's own documentation and support. Issues referenced for context: anthropics/claude-code #65889 (silent routing under a pinned model), #65899 (stats "favorite model" ignores recency).