CI check

Make your CI fail before your model does.

Your provider emails the account owner when a model is deprecated. Your build system never finds out. check-models closes that gap: point it at your repo, and it fails the build while you still have months, not while you have an incident.

74
of 204 tracked models are already deprecated or retired
13
retire within the next 90 days — 3 of them not yet marked deprecated
5 Aug 2026
catalog last verified against provider docs

Thirty seconds

curl -sLO https://aimodelwatch.dev/ci/check-models.mjs
node check-models.mjs --scan .

No install, no dependencies, no key, no signup — one file and Node 18+. The scan reads your source for anything that looks like a model id we track, so there is nothing to configure on the first run.

What it looks like when it fires

$ node check-models.mjs --scan .
check-models 1.1.0 — feed updated 2026-08-05, 204 models
scanned . — found 3 model ids in your source
  claude-mythos-preview  src/llm.ts:14
  gpt-5.5-pro            src/llm.ts:15
  gemini-3.5-flash       config.yaml:7
checked 3 models · retirement threshold 90 days

✖ claude-mythos-preview: Claude Mythos Preview is DEPRECATED. Retires 2026-06-30.
  Provider's stated replacement: claude-mythos-5.

1 problem, 0 warnings.
$ echo $?
1

In GitHub Actions

There is an action, so this is the whole setup — no install step, no setup-node, no inputs:

# .github/workflows/model-lifecycle.yml
name: model lifecycle
on:
  pull_request:
  schedule: [{ cron: '0 7 * * 1' }]   # and once a week, so it catches drift with no PR

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Khavel/check-models-action@v1

With no inputs it walks the repo, finds every model id in your source and checks all of them. Findings become inline ::error annotations and a job-summary table. The action vendors the script, so @v1 pins the code while the catalog stays live — source and inputs. The weekly cron is the half that matters: a deprecation lands on a day nobody opened a PR.

Reading the results when it fails. GitHub does not evaluate a composite action's outputs when the action fails the job, so steps.*.outputs.* come back empty on exactly the path you care about. The JSON report is always written — point report-path somewhere and read it with if: always(), or set warn-only: true and branch on the outputs yourself.

Prefer no third-party action in your pipeline? The script is still one file and still the honest MVP — curl -sLO https://aimodelwatch.dev/ci/check-models.mjs then node check-models.mjs --scan ., on any CI.

What it checks

SignalMeaningResult
RetiredThe provider has switched the endpoint off. Your call is already failing, or will on the next deploy.fails
DeprecatedThe provider has declared it end-of-life. Still answering, on a clock.fails
Retiring soonretires_on is within --within days (default 90).fails
Re-pricedInput, output or cached price differs from your pinned snapshot. Needs --pin.fails
Lifecycle movedThe retirement date or the stated replacement changed since your pin.fails
Unknown idWe don't track that id — a typo, or a model we haven't added yet.warns

Every one of these is what the provider states, read from its own documentation — never inferred by us. Where a provider publishes nothing, the field is null and the check stays quiet rather than guessing.

Catching a price change

Nobody emails you when a model gets cheaper or dearer. Pin the numbers you costed your product on, commit the file, and the diff shows up in a pull request like any other change:

node check-models.mjs --scan . --pin .amw-pin.json --update-pin   # once, then commit
node check-models.mjs --scan . --pin .amw-pin.json                # every build

✖ claude-opus-4-5: Claude Opus 4.5 input price ($/1M) changed since your pin: 5 → 4.

Options

FlagWhat it does
--scan <dir>Walk a directory and find every model id or API string we know about. No config file.
--file <path>Read ids from a newline-delimited file, a JSON array, or {"models":[…]}.
--models a,bIds inline. Bare positional arguments work too.
--within <days>Retirement horizon that fails the build. Default 90.
--pin <file>Compare against a pinned snapshot — this is what catches a price change.
--update-pinWrite the current values to --pin and exit 0. Commit the file.
--jsonMachine-readable report on stdout.
--json-out <path>The same report, written to a file. Use this one inside GitHub Actions — there the ::error annotations share stdout with --json.
-w, --warn-onlyReport, never fail. Good for the first week.
--strict-unknownTreat an unrecognised id as a failure instead of a warning.
--cache <dir>Store the feed + ETag; an unchanged feed then costs a 304, not a download.

Exit codes: 0 clean · 1 findings · 2 usage or network error. A network failure never fails your build as if a model were dead — it exits 2, distinctly.

What it sends us

Two GETs to the public feed — the same URL anyone can curl. No repo contents, no ids, no telemetry: the scan happens entirely on your machine, and only the models you ask about are ever resolved locally against the downloaded catalog. The script identifies itself as amw-ci-check/1.1.0 in its User-Agent, which is how we count how many projects depend on this data. Strip it if you'd rather not be counted; nothing breaks.

MIT — use it in commercial products, fork it, vendor it into your repo. It is a plain file on purpose: read it before you run it. The data behind it is the same free JSON API and open dataset, refreshed daily from official provider documentation. Something wrong, or a check you want?Tell us.