Find hardcoded Anthropic model IDs in your code that are retired or about to be.
A retired model does not slow down or get worse. It stops. Anthropic's own policy says it plainly: "Requests to retired models will fail." So a file that pins a retired model ID keeps working right up until the retirement date, and then every call it makes returns an error. The failure is sudden and it lands on a date you can read off a calendar today.
The closest live example: claude-opus-4-1-20250805 retires on 2026-08-05. If
that string is baked into your source, your Opus 4.1 calls break that day.
sunset-scan walks a directory, finds those IDs, and tells you what to swap each
one for. It is a single Python file with no dependencies, so it drops into a CI
job without a build step.
For every retired or dying model ID it finds, it prints one line:
path/to/file.py:42: claude-opus-4-1-20250805 (dying, retires 2026-08-05, move to claude-opus-4-8)
It exits non-zero when it finds anything, so a CI job fails the moment a retired ID lands in your tree. A clean scan exits zero and prints nothing but a summary.
No install, no packages. You need Python 3.6 or newer.
# scan the current directory
python3 sunset_scan.py
# scan a specific path
python3 sunset_scan.py path/to/project
# or use the entrypoint
./sunset-scan src/
# just the hit lines, no summary
python3 sunset_scan.py -q .
# see the built-in model list
python3 sunset_scan.py --list-modelsExit codes: 0 clean, 1 at least one retired or dying ID found, 2 usage
error (for example, a path that does not exist).
Because a hit exits non-zero, the check is one line. GitHub Actions:
name: sunset-scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python3 sunset_scan.py .Any other runner works the same way. Run python3 sunset_scan.py . and let the
exit code fail the build. Pre-commit hook, Makefile target, git hook: same idea.
The model IDs are built in, taken from the Model Graveyard data below. It matches
the full dated ID (claude-opus-4-1-20250805) and the shorter form people also
hardcode (claude-opus-4-1), then reports both as the canonical dated ID. It
guards against version-number false positives, so a future claude-opus-4-10
will not trip the claude-opus-4-1 pattern, and the replacement IDs you are
supposed to move to (like claude-opus-4-8) are never flagged.
It reads UTF-8 text files, skips the usual build and vendor directories
(.git, node_modules, __pycache__, venv, dist, build, and friends),
and skips binaries by extension.
The known-model list, with counts of public files still pinning each ID:
| Model ID | Status | Retirement date | Public files still pinning it | Move to |
|---|---|---|---|---|
claude-opus-4-1-20250805 |
Dying | 2026-08-05 | 10,536 | claude-opus-4-8 |
claude-sonnet-4-20250514 |
Retired | 2026-06-15 | 46,336 | claude-sonnet-4-6 |
claude-3-5-sonnet-20241022 |
Retired | 2025-10-28 | 16,304 | claude-sonnet-4-6 |
claude-3-haiku-20240307 |
Retired | 2026-04-20 | 6,000 | claude-haiku-4-5-20251001 |
claude-3-opus-20240229 |
Retired | 2026-01-05 | 4,420 | claude-opus-4-8 |
Source: the "public files" column is GitHub code-search index counts from exact phrase queries, and the dates are from Anthropic's model deprecations page. Both were measured on 2026-07-22. GitHub's count covers public, indexed files only, so treat it as a floor, not a census. The four already-retired IDs sit in more than 73,000 public files that error on a live call right now.
Changing the string is often not the whole job. Moving off Opus 4.1 to Opus 4.8,
for instance, is more than a rename: temperature, top_p, and top_k return a
400 on Opus 4.7 and later when set to a non-default value, and a couple of other
request parameters changed too. Check your call sites, not just the ID. Anthropic's
migration guide covers the details.
Built by toolshed (maker Cal). We also do model migrations, so if you find a pile of these and want help moving off them, get in touch. The scanner is free and MIT licensed. Use it however you like.