On Sunday morning I made coffee and opened the official model table. Sonnet 4.5 retires this month, and I wanted to reread the table once before November got busy.
One row stopped me. claude-opus-4-5-20251101 had "Not sooner than November 24, 2026" in the retirement column. That is next month, and for a second my stomach dropped.
Then I looked at the neighboring columns. The state was Active, and the Deprecated column said N/A. No notice had gone out. The "not sooner than" date and the day a model actually stops turned out to be two different things.
This post sorts out that difference using the table as it stood on November 1. I also include a small script that counts model IDs in a folder and checks them against the table, along with what it printed when I ran it on my own working folders. I'm an indie developer, and I've lost track of which model names I typed into which helper script.
The four words in the table, and the rows that matter to me
The Model deprecations page sorts every model into one of four states.
| State | What the docs say (paraphrased) | What you do |
|---|---|---|
| Active | Fully supported and recommended | Nothing. The date column is a floor, not a deadline |
| Legacy | No more updates, may be deprecated later | Keep a replacement in mind |
| Deprecated | Still works, not recommended; a replacement and a retirement date are set | Move before the date. This date is a real deadline |
| Retired | Unavailable; requests fail | Fix any leftover reference right now |
From the table as of November 1, I picked the three rows that touch my own work.
| Model ID | State | How to read the date |
|---|---|---|
| claude-sonnet-4-5-20250929 | Deprecated | Retires 2026-11-30 (replacement: claude-sonnet-5-5). 29 days left |
| claude-opus-4-5-20251101 | Active | Will not retire before 2026-11-24. No notice yet |
| claude-haiku-4-5-20251001 | Active | Will not retire before 2026-10-15. That date has passed, still no notice |
All three look like "rows with a date." Only the top one is a deadline.
A "not sooner than" date is a floor, not a stop date
The reasoning comes from the Notifications section of the same page. For publicly released models, Anthropic says it gives at least 60 days' notice before retirement.
The Sonnet 4.5 row follows that promise exactly. The notice went out on September 30, the retirement date is November 30, and the gap is 61 days. The moment the state became Deprecated, the deadline existed.
Opus 4.5 still has N/A in the Deprecated column. Read literally, a model nobody has been notified about has no November 24 deadline. "Not sooner than" only says the model won't be switched off before that day.
Here is the line I drew for myself.
A date is a deadline only on a row that has become Deprecated. On an Active row, I treat the date as a signal to start watching.
Of course, the closer the floor gets, the likelier a notice becomes. So the Opus 4.5 row doesn't mean "ignore it." It means "know where it's referenced now, so that when the email arrives, I can move the same day." I handled the Haiku 4.5 row the same way, and I wrote up how to read a passed date with no notice in Haiku 4.5's October 15 Floor Has Passed, but No Stop Date Has Been Set.
One more caution sits at the top of the page. These dates apply to Anthropic-operated platforms: the Claude API, Claude Platform on AWS, the Anthropic-managed Google Cloud offering, and Microsoft Foundry. Amazon Bedrock and the Google-managed offering set their own retirement schedules, so the same model can have different dates. If you reach Claude through more than one route, you need to read two tables.
Counting my own model IDs in 60 lines
Once the table makes sense, the next question is where those IDs actually sit in my own files. My working folders hold a handful of chores I've handed to AI, and I no longer remember which model name went where.
So I wrote a script that scans text files in a folder, pulls out model IDs, and compares them with a hand-copied version of the retirement table. It skips node_modules and similar folders, and unreadable files simply don't count.
#!/usr/bin/env python3
"""フォルダ内のテキストファイルから Claude のモデル ID を拾い、退役表の状態と突き合わせる。"""
import re
import sys
from collections import Counter
from pathlib import Path
# 2026-11-01 時点の公式「Model deprecations」表を手で写したもの。更新日を必ず併記する
STATE = {
"claude-sonnet-4-5-20250929": ("Deprecated", "2026-11-30 退役"),
"claude-opus-4-5-20251101": ("Active", "2026-11-24 より早くは退役しない"),
"claude-haiku-4-5-20251001": ("Active", "2026-10-15 より早くは退役しない(日付は経過)"),
"claude-opus-4-6": ("Active", "2027-02-05 より早くは退役しない"),
"claude-sonnet-4-6": ("Active", "2027-02-17 より早くは退役しない"),
"claude-sonnet-4-20250514": ("Retired", "2026-06-15 退役済み"),
"claude-opus-4-20250514": ("Retired", "2026-06-15 退役済み"),
"claude-opus-4-1-20250805": ("Retired", "2026-08-05 退役済み"),
}
# 日付つきの形(-20250514)を先に試す。順序を逆にすると日付の先頭 1 桁だけ拾って別の ID に化ける
PATTERN = re.compile(r"claude-(?:opus|sonnet|haiku)-\d(?:-\d{8}|-\d(?:-\d{8})?)?")
TEXT_SUFFIX = {".py", ".js", ".ts", ".json", ".yaml", ".yml", ".toml", ".sh", ".md", ".txt", ".env"}
def scan(root: Path) -> Counter:
found = Counter()
for path in root.rglob("*"):
if not path.is_file() or path.suffix not in TEXT_SUFFIX:
continue
if any(part in {"node_modules", ".git", ".next"} for part in path.parts):
continue
try:
text = path.read_text(encoding="utf-8")
except (UnicodeDecodeError, OSError):
continue # 読めないファイルは黙って飛ばす(件数には入らない)
found.update(PATTERN.findall(text))
return found
def main() -> None:
root = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
found = scan(root)
print(f"{'モデル ID':36} {'件数':>4} {'状態':10} 備考")
for model, count in found.most_common():
state, note = STATE.get(model, ("表に無い", "公式の表で確認"))
print(f"{model:36} {count:>4} {state:10} {note}")
if __name__ == "__main__":
main()Usage is one argument, the folder to inspect.
python3 model_id_state.py ~/my-scriptsI made two decisions up front. First, an ID that isn't in the table is printed as "not in table" instead of being dropped. Silence and "needs checking" must never look the same. Second, the state table carries a date. A hand-copied table starts going stale the moment you copy it.
My first run turned one ID into another
I ran the first version on two of my folders, and the output contained claude-opus-4-2 and claude-sonnet-4-2, four hits each. Neither ID exists.
The cause was the order of alternatives in the regex. For an ID like claude-opus-4-20250514, the "hyphen plus one digit" part grabbed the 2 at the start of the date, and the remaining seven digits had nowhere to go, so they were thrown away.
The fix was to try the dated form first.
# Before: grabs the first digit of the date and yields a different ID
re.compile(r"claude-(?:opus|sonnet|haiku)-\d(?:-\d)?(?:-\d{8})?")
# After: dated form first, then generation form
re.compile(r"claude-(?:opus|sonnet|haiku)-\d(?:-\d{8}|-\d(?:-\d{8})?)?")After the fix, the same eight hits became claude-sonnet-4-20250514 and claude-opus-4-20250514, and both were classified as Retired. In other words, four references to each already-retired model were sitting in my notes, and the broken version had hidden them.
Reading the result: a big count is not a big risk
Here are the results for the two folders side by side.
| Folder | IDs found | What it told me |
|---|---|---|
| Instructions and scripts | 6 kinds, 1 hit each. No Deprecated or Retired | No call site that hits the deadline |
| Research notes | 19 kinds. Sonnet 4.5 had 27 hits, Retired IDs had 10 in total | Mentions in notes, not calls |
In the notes folder, the soon-to-retire claude-sonnet-4-5-20250929 showed up 27 times. My heart jumped, but when I opened the files, every hit was a note I had written while researching the retirement. None of them sat in a request.
A big count is not a big risk. In a folder full of notes, the Deprecated count is not an alarm by itself. I decided to count executable files (.py, .js, .sh, configs) separately from reading material (.md, .txt).
The other thing I noticed was that the "not in table" bucket was the longest. Newer IDs such as claude-opus-4-8 aren't in my hand-copied table, which doesn't mean they're dangerous. Still, when most IDs are missing from the table, the comparison loses its point. Checking against the official table at the start of each month and adding rows is a chore I can't skip.
What to do this week
If I had to pick one action, it's this.
Search only your executable files for claude-sonnet-4-5-20250929. Zero hits means the November 30 deadline doesn't reach your files. If you find one, set aside time to compare outputs on the same inputs before switching to claude-sonnet-5-5.
How to count call sites from the Usage page CSV is covered in 43 Days Until Sonnet 4.5 Retires: I Counted Callers From the Usage CSV, Not From grep. A script that searches files and your actual usage history look in different places. Layering the two is, I think, what really shrinks the gaps.
I plan to run the script once more before the day ends and leave the result for Monday's me.