$ npx driftcite .
feed: live | 0 dependencies resolved
4 provider findings (4 breaking)
[BREAKING] openai/model_id/text-davinci-003 -- retired (DIED 945 days ago, 2024-01-04)
Shut down. Requests fail.
use instead: gpt-5.6-terra
evidence: https://developers.openai.com/api/docs/deprecations
models.py:6 LEGACY = "text-davinci-003"
[BREAKING] openai/model_id/gpt-4-vision-preview -- retired (DIED 608 days ago, 2024-12-06)
Shut down. Requests fail.
use instead: gpt-4o
evidence: https://developers.openai.com/api/docs/deprecations
models.py:8 VISION = "gpt-4-vision-preview"
[BREAKING] openai/model_id/gpt-4-turbo -- deprecated (breaks in 78 days, 2026-10-23)
Shutdown announced for 2026-10-23.
use instead: gpt-5.6-sol
evidence: https://developers.openai.com/api/docs/deprecations
models.py:7 CHAT_MODEL = "gpt-4-turbo"
[BREAKING] stripe/endpoint//v1/invoices/upcoming -- removed
GET /v1/invoices/upcoming existed in 2024-06-20 and is gone in 2026-06-24.
evidence: https://github.com/stripe/openapi/compare/v1200...v2345
billing.js:4 return stripe.request("GET", "/v1/invoices/upcoming", { customer });That is a whole run, on 2026-08-06. The only line removed is the one naming the directory it was pointed at; no finding was dropped to make the output look tidier. The day counts are the part that moves: they are computed against the day the command runs, so they will not read 945 and 78 when you run it.
Runs on your machine. Nothing is uploaded. No account.
Your lockfile does not move when a provider removes an endpoint, drops a request field, or shuts down a model. Nothing in your dependency tree changes. Every dependency tool reads manifests, so every dependency tool is blind to this by construction.
Reading the changelog is not enough either. CircleCI went down for 1h50m in March 2022 after an upstream endpoint moved, and their postmortem says it better than we could:
We realized during the incident that although we had been notified several times about this change, we had not realized that it would affect us.
They were told. Repeatedly. Over two years. Somebody still had to map a prose announcement onto their own call sites, and nobody did.
|
A manifest asserts, the scanner locates, and no language model sits anywhere in the detection path. When we say Stripe removed You should not have to trust us. |
An entry reading "deprecated, retires 2026-10-23" is a countdown, not a footnote, and becomes retired the moment that date passes. Findings sort by how long you have left: No dependency tool has a field for time remaining. |
|
Hosted API drift Generated by diffing two versions of a provider's own published OpenAPI spec. Removed endpoints, dropped parameters, retired enum values. Nothing hand-written, nothing inferred. python3 scanner/openapi_diff.py \
--provider stripe --from v1200 --to v2345 |
Maintainer-flagged versions npm carries a per-version Lockfiles read: RubyGems publishes no yanked flag — a yanked version simply stops being served — so that finding says exactly that and never claims the maintainer deprecated anything. urllib3@1.25
"Broken release"
basic-ftp@4.6.6
"Security vulnerability fixed in 5.2.1, please upgrade" |
Sweep every repository you own at once:
python3 scanner/sweep.py --parent ~/code --match '*'npx driftcite . --fix # show the swaps
npx driftcite . --fix --write # apply them models.py:8
- VISION = "gpt-4-vision-preview"
+ VISION = "gpt-4o"
https://developers.openai.com/api/docs/deprecationsThere is no model in this path either. It swaps a string the provider retired for the string the provider named, inside the quoting your code already uses.
When the replacement is prose rather than a drop-in token, it refuses and says so:
1 finding(s) need a human
stripe/endpoint//v1/invoices/upcoming
the provider named no replacementImportant
Comment lines are never edited, only the lines it reported may change, and if an edit would touch anything else the file is left alone entirely.
Most codebases will have findings on day one, and a build that fails forever is a build people delete the check from. Two ways to accept what is already there without going blind to what arrives next.
A baseline. Accept today's findings, fail only on new ones:
npx driftcite . --write-baselineThis writes .driftcite-baseline.json. Everything already broken is accepted;
anything new still fails. Delete an entry once you fix it.
An ignore file for permanent decisions, .driftciteignore:
legacy/* # a path
openai/model_id/text-davinci-003 # an artifact, anywhere
examples/* :: google/model_id/gemini-2.0-flash # that artifact, only there
Suppressed findings are counted and reported as suppressed, never silently dropped. A tool that hides things is worse than one that annoys.
- uses: nilaypatell/driftcite@v0.2.1Fails the build on breaking drift and writes the findings, with evidence links, into the job summary. Inputs: path, fail-on-breaking, check-dependencies, offline, slack-webhook.
Not on GitHub? The Action is a thin wrapper; the CLI is the product and runs in any CI. GitLab:
driftcite:
image: node:22
script: [npx driftcite .]The same one-liner works in Bitbucket Pipelines, Jenkins, or a plain cron job on a server. Exit 1 on breaking drift is the whole interface.
Slack. --slack posts breaking findings to a Slack incoming webhook, with the evidence links and the provider-named replacements in the message. The URL is read from DRIFTCITE_SLACK_WEBHOOK — environment, not argv, so it never lands in shell history. Only breaking findings post; a clean scan says nothing, because a daily "all clear" is how a channel gets muted. Schedule it with a baseline committed and the channel only ever hears about drift that is new:
- uses: nilaypatell/driftcite@v0.2.1
with:
slack-webhook: ${{ secrets.DRIFTCITE_SLACK_WEBHOOK }}$ DRIFTCITE_SLACK_WEBHOOK=https://hooks.slack.com/… npx driftcite . --slackCalendar. Every dated shutdown in the feed, as an all-day event. Subscribe once, in any calendar app:
https://raw.githubusercontent.com/nilaypatell/driftcite/main/feed/calendar.ics
RSS. Every artifact that appears, every status that moves, and — because providers really do this — every time a shutdown date itself is moved:
https://raw.githubusercontent.com/nilaypatell/driftcite/main/feed/changes.xml
All three carry the same evidence links as the scan output. Nothing in an alert is a guess.
Two ways, one outcome: a PR arrives with the fix already applied and the provider's own page cited.
Install the GitHub App: github.com/apps/driftcite. Nothing to copy into your repo. The App holds exactly two permissions (contents and pull requests), stores only the artifact IDs your code matched — never your code — and opens one PR per repository when a provider retires something you call. Here is a real one it opened.
Or keep it entirely in your own CI: copy
driftcite-autofix.yml into your
own .github/workflows/. On a schedule it scans, applies only the fixes the
provider itself named, and opens the same pull request from inside your repo.
- const MODEL = "text-davinci-003";
+ const MODEL = "gpt-5.6-terra";
- const VISION = "gpt-4-vision-preview";
+ const VISION = "gpt-4o";It fixes what it is certain about and refuses the rest out loud:
1 finding(s) need a human
stripe/endpoint//v1/invoices/upcoming
the provider named no replacementIt only ever runs against the repository it is installed in, and it will not reopen a pull request you have left sitting. Automatic pull requests are welcome when you asked for them and spam when you did not.
| Where it runs | What you get | |
|---|---|---|
npx driftcite . |
your machine | what is broken right now |
| GitHub Action | your CI | the same, every PR, exit 1 on breaking |
| Autofix workflow | your CI | a pull request with the fix already applied |
| The App | our sweep | a PR in every installed repo when a provider moves |
The first two answer what is broken now. The third closes the loop yourself. The fourth closes it for every repository at once, without a workflow file in any of them, and runs even while your laptop is asleep.
Note
Your source code never reaches us at any tier. The hosted watch stores only the artifact IDs your code matched, a few hundred strings, never files.
The first version reported 86 findings on a real 1,200-dependency repository and roughly a quarter were real. The parameter refund was matching inside "refunded" and inside a sentence about refunds. A retired enum value like hosted is an ordinary English word.
Each artifact kind now matches only in the shape it actually takes when sent to a provider:
| Kind | Matches as |
|---|---|
enum_value |
a quoted string literal |
request_param |
a quoted literal or an object key |
model_id · endpoint |
a quoted literal or a bounded token |
Kinds other than model IDs also require the file to reference that provider at all. Comment lines are skipped. Vendored manifests are ignored.
A tool that is wrong three times out of four gets muted, then deleted. Coverage has gone from 29 artifacts to 235 since then without loosening one match shape, and 24 of the 179 tests exist for no other purpose than to stop it being loosened: refund inside "refunded", hosted as an ordinary English word, a literal quoted inside a comment. Every one of them was a finding this scanner produced before it was a finding this scanner refuses. Coverage is worthless if it arrives with noise.
| Provider | Source | Artifacts |
|---|---|---|
| OpenAI | openai/openai-openapi, deprecations page, Responses migration guide and the v1 SDK migration |
120 |
| Mistral | model docs and SDK migration guide, curated | 45 |
| Square | square/connect-api-specification |
41 |
| Azure | Foundry model retirement schedule, curated | 38 |
| Datadog | DataDog/datadog-api-client-python |
36 |
| Cloudflare | cloudflare/api-schemas |
34 |
| Kubernetes | kubernetes/kubernetes API spec diffs |
27 |
| GitHub | github/rest-api-description |
26 |
| Gemini deprecations, changelog and SDK migration guide | 23 | |
| Anthropic | model deprecations and migration guide, curated | 21 |
| AWS | Lambda runtime schedule and the SDK v2 end-of-support notice, curated | 21 |
| Cohere | deprecations page and v1-to-v2 guide, curated | 19 |
| Bedrock | AWS model lifecycle page, curated | 17 |
| Algolia | algolia/api-clients-automation |
21 |
| Google Cloud | Cloud Functions runtime schedule, curated | 11 |
| Perplexity · Together · xAI | deprecation pages and changelogs, curated | 24 |
| Stability · ElevenLabs · AssemblyAI · Fireworks · DeepSeek | deprecation pages and changelogs, curated | 19 |
| Stripe | stripe/openapi · tags v1200 through v2375 |
8 |
| Meta · Salesforce · Google Ads · Shopify | API version sunset schedules, curated | 14 |
| Twilio · Heroku · Firebase | product EOLs, stack retirements, compat deprecation | 6 |
| DigitalOcean · Groq · Adyen · Plaid | specs and deprecation pages | 8 |
| Discord · SendGrid · Intercom · DocuSign · PagerDuty · ngrok · Okta · Elastic · Netlify | specs tracked, no drift observed yet | 0 |
| Asana · Box | tracked, currently no drift | 0 |
| npm · PyPI · crates.io · RubyGems | every package, no per-provider work | live |
Thirty-five providers in the published feed, 579 artifacts, every one carrying the provider's own evidence URL. Forty-six are tracked in total. Of the eleven spec repositories added on 2026-08-07, two produced artifacts on their very first historical diff — Kubernetes and Algolia — and the other nine are polled and quiet, which is what a fresh spec under watch looks like.
Coverage grows where findings fire, not where rows are cheap. Measured across 467 real repositories, model IDs and SDK renames produce almost all true findings, so the 2026-08-07 expansion went there first: eight AI providers' model retirements, the API-version sunset schedules of Shopify, Meta, Salesforce and Google Ads, the Lambda and Cloud Functions runtime retirement calendars, and the SDK breaks no lockfile tool can see (openai-python v0, aws-sdk v2, Firebase compat). Where a provider retires by redirect or fall-forward — xAI, Shopify, Meta — the artifact says so and carries no deadline, because nothing hard-fails on the date.
Azure's rows are in the feed again. They were withheld while the published
client predated require_context, because a reseller's retirement date
reported to somebody calling the vendor directly is a wrong answer with a
citation attached. 0.2.1 reads the gate, so the data can follow it.
Two of them resell other people's models on their own clock. Azure retires
gpt-4o and claude-sonnet-4-5 on dates their original vendors do not
recognise, and AWS states the same thing outright: "only the dates on this
page apply." The two are handled differently because they name models
differently. Bedrock's IDs carry the reseller in the literal —
anthropic.claude-3-5-sonnet-20240620-v1:0 cannot be a direct Anthropic call
— so those 17 report anywhere. Azure serves the bare vendor ID, so every one
of its artifacts sets require_context and is reported only in files that
name Azure. A right answer delivered to the wrong caller is a wrong answer.
The same gate guards Together's artifacts, whose HuggingFace-style ids are
also how vLLM configs and relays spell models, and the runtime artifacts,
because "python3.9" is only a finding in a file that names its platform.
The registries are the cheapest coverage in software: one cursor covers every npm package, one header covers every PyPI project.
Providers live in providers.yaml, not in code, so adding one is a data change rather than a patch:
stripe:
repo: stripe/openapi
path: openapi/spec3.json
markers: [stripe, STRIPE_SECRET, STRIPE_API]markers matter more than they look. They are what stops a retired parameter named refund from flagging every codebase that has ever mentioned a refund.
Full guide in CONTRIBUTING.md. Pull requests welcome.
npx driftcite . --init-localThis writes .driftcite-local.json at your repo root. Put feed-shaped
artifacts in it — same fields, same rules — and the next scan reports them
alongside the public feed. It exists for the APIs the public feed cannot
carry: your internal services, a vendor too small for the feed, or a provider
whose pull request here hasn't merged yet.
The rules are not relaxed because the file is yours. Every artifact needs an
evidence URL (your wiki counts), distinctive literals, and file_markers for
any kind other than a model ID; an artifact that fails validation is skipped
and named, and a literal the public feed already carries loses to the feed —
two death dates on one line of code is the bug, whoever writes the second
one. If the API is public, upstream it: a spec repo is four lines in
providers.yaml, a deprecations page is a curated manifest.
Manifests live in manifests/, are regenerated daily by a scheduled workflow in this repository, and are committed here in the open. The git history is the record of what was observed, and when.
The clock does more than diff specs. Every documentation page a curated
manifest cites is watched by content hash, so a provider editing a
deprecations page becomes a work order instead of silent rot. Every provider's
live model list is probed daily where a key is configured: a retirement is then
recorded because the ID stopped being served, not only because a page said so.
And the endpoints themselves are asked whether they are dying, by HEAD, for the
Sunset and Deprecation headers of RFC 8594 and RFC 9745 — twelve paths
across GitHub, Stripe and Cohere on 2026-08-06, none of which sent either one.
All three records are committed here, in the open, like everything else.
Every fact is public and free to read. You can vendor the whole thing. The hard part was never obtaining it, it is maintaining it, every day, forever, across every provider, because it decays the moment anyone stops.
| Component | License |
|---|---|
| Scanner · differ · manifest schema | Apache 2.0. Embed it, fork it, ship it inside your own tooling |
| Published manifests | Data license. Free to use, redistribute and research; not for repackaging as a competing feed |
This split is deliberate. The scanner is permissive because we want it embedded everywhere, including in other people's pipelines. The work that has to be redone every single day is the feed, so that is where the one restriction sits.
The underlying facts are not ours and never could be, which is exactly why every artifact cites the provider who published it.
- ARCHITECTURE.md: the moat, polling cadence against real API limits, hosting costs, pricing
- spec/MANIFEST.md: the drift manifest format
scanner/build_feed.py printed; the transcripts are whole runs, untrimmed; the day counts inside them are computed against the day the command runs and will differ from yours.