modelrot
claude-3-5-haiku-20241022 stopped working on 19 February 2026.
gpt-3.5-turbo switches off on 23 October 2026. gpt-4-turbo the same day.
Your code names a model in six places — a call, a constant, a deploy file, a notebook, a test fixture, a README — and none of them announce the day the provider turns it off. You find out when the calls start failing.
pip install modelrot
modelrot
No API key. No network. No cost. It reads files.
What it tells you
high "claude-3-5-haiku-20241022" was retired on 2026-02-19
app/summarise.py:4, tests/fixtures.py:12
Calls naming this model do not work any more. The provider recommends
claude-haiku-4-5-20251001.
"anthropic lists claude-3-5-haiku-20241022 as retired."
https://platform.claude.com/docs/en/about-claude/model-deprecations
high "gpt-3.5-turbo" is switched off in 32 days, on 2026-10-23
config/deploy.yaml:1
It still works today. On that date it stops, with no further notice.
The provider recommends gpt-5.6-terra.
high temperature passed to claude-opus-4-7, which rejects it
app/summarise.py:9
The Python SDK (v1.0 and later) removes these parameters from request
types, so passing them raises a TypeError. On the wire it is a 400.
Omit them and use prompting to guide model behaviour.
That last one is code that is already broken and does not know it: on Claude
4.7 and later, a non-default temperature, top_p or top_k returns a 400 —
and the Python SDK raises a TypeError before the request leaves the process.
The same rule exists on the other side: GPT-6 Astra does not accept
temperature, top_p or top_logprobs either.
Severity is days, not labels
Most tooling calls a retired model and a deprecated one the same thing and gives them the same yellow warning. One is a note for next quarter; the other is production, already down.
- retired → high
- deprecated, switching off within 90 days → high, with the countdown in the title
- deprecated, further out → medium
A shutdown crossing the 90-day line escalates on its own. You do not need a new version of modelrot for that to happen.
Every finding quotes the provider
modelrot tells you your code is broken because of a decision another company
made. If it is wrong about that, it sends you to change working code — so every
finding carries the provider's own page and a sentence from it. Anything
without a published sentence behind it is printed as
(inferred, not a published rule) and never dressed up as one.
Where it looks
Python files are parsed with ast, which is what lets temperature=0.2 be
read together with the model= beside it. Configuration is searched as text:
.yaml, .yml, .toml, .json, .ini, .cfg, .env, .sh, .md.
The model name that survives a migration is almost always the one in a deploy file nobody greps.
What it cannot see
- Anything that is not a literal. A model name built at runtime from an env var or a settings object is invisible. Always will be.
- Models it does not know. The catalog is a dated snapshot of what Anthropic and OpenAI publish. A model retired after that date is not in it, so its absence from the report means nothing — and past 45 days the report says so in yellow rather than staying quiet. Only those two providers so far.
- Unknown model names. A string that is not in the catalog is ignored, so a typo in a model id currently looks exactly like a healthy call.
- Whether any of it is a good idea. modelrot does not know if the model is right for the job, what it costs, or whether your prompts still work after a swap.
There is no green tick. A clean run prints what was checked and what was not.
In CI
- run: pipx run modelrot --fail-on medium
Exit code is 1 at high by default. --fail-on medium catches a shutdown
before it is urgent; --fail-on none never fails the build.
Options
modelrot [path] scan a directory (default: the current one)
--json machine-readable, same fields as the report
--inventory list every model found, healthy ones included
--no-sources omit the quoted provider documentation
--fail-on high|medium|none exit code threshold (default: high)
Where the data comes from
Both providers serve a machine-readable version of their deprecation page:
append .md to the documentation URL. OpenAI documents it; Anthropic links its
own from llms.txt. modelrot parses those, so the catalog is read from the
source of truth rather than transcribed from it.
Every row records the URL it came from, the sha256 of the document at the time, and when it was fetched — so any claim can be diffed back to bytes.
python3 scripts/refresh_catalog.py --check # how old is the snapshot?
python3 scripts/refresh_catalog.py --refresh # fetch, parse, write
The repository re-reads both pages weekly and opens a pull request when anything moved, with a row-level summary rather than a hash. It never merges on its own: every row is a claim about someone else's product.
A refresh merges. A model that disappears from a provider's page keeps its row, because a model vanishing is the event this tool exists to report — and it is why the aggregated catalogs are not used as a source. LiteLLM, models.dev and OpenRouter are live routing tables: they delete their dead.
As a pre-commit hook
- repo: https://github.com/sheldor26/modelrot
rev: v0.1.0
hooks:
- id: modelrot
Requirements
Python 3.9 or newer. No dependencies.
License
MIT
Metadata
Release files for modelrot 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| modelrot-0.1.0.tar.gz | 46.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| modelrot-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 65.4 kB
Release files / modelrot-0.1.0.tar.gz
| Download URL | modelrot-0.1.0.tar.gz |
|---|---|
| Size | 46.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7d9cfcf7683222bdd2a91407061ffb64d8af12f5f75c6ce575defcde5fe9311f
|
|
BLAKE2b-256 checksum How to use checksums |
5ff917c8888a6103ad29bf943ad1982611db4be271e74fa3bf91cfb3f018b896
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / modelrot-0.1.0-py3-none-any.whl
| Download URL | modelrot-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6f16fc3d7c58d81a2d105ea0e49309224fa7def25a998130ae74506790bc9459
|
|
BLAKE2b-256 checksum How to use checksums |
80c4b87ff77fd99d6c43b080bfaba38d3bcb024d1554051d71132d4efdc04e0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|