API providers publish retirement dates. drift-scan finds the call sites in your code that are about to break.
uvx drift-scan https://github.com/OWNER/NAME
Also takes a local path. Requires Python 3.12+ and, for URLs,
git. Exit codes: 0 nothing found, 1 exposure
found, 2 error - so it works as a CI gate with no further wiring.
A real scan of a real repository. Nothing here is mocked up.
drift exposure - drift-labs/drift-fork-rehearsal
https://github.com/drift-labs/drift-fork-rehearsal.git @ a5361faeecf5
scanned in 0.8s | providers: anthropic, openai, stripe
EXPOSURE (1)
DEPRECATION gpt-5.2-chat-latest - gpt-5.2-chat-latest - Shutdown date: Aug 10, 2026 - Recommended replacement: gpt-5.6-sol | 14d left
summarise.py:15 openai.chat.completions.create [direct] conf=1.0
NO EXPOSURE (153) - anthropic 19, openai 134 - no matching call sites (--verbose to list)
COVERAGE
python: 100.0% (2/2; skipped none)
typescript: 0.0% (0/0; skipped none)
(other): 0.0% (0/2; skipped unsupported_language=2)
29 path(s) under vendored or ignored directories were excluded from the totals above, not skipped within them
LIMITATIONS (generated)
- cross-file constants and values imported from other modules
- names bound to more than one literal at one level of the scope chain — drift refuses to guess which one reaches the call, and discloses the site instead
- attributes defined in a base class, a mixin, or a metaclass (inheritance is not followed)
- f-strings, template literals containing substitutions, and strings built by concatenation or .format
- values read from config files (yaml/json/toml/.env) at runtime
- dataflow beyond the scope chain (chained reassignment, function returns, comprehensions)
- options-object spreads beyond a single variable
- endpoint route -> SDK symbol mapping (only exact symbol matches; ADR 0007)
- files in unsupported languages were skipped (see coverage)
SUMMARY: 1 site(s) across 1 change(s); highest severity DEPRECATION; soonest sunset in 14d
Change data: 154 retirement(s), generated 2026-07-27 (0 day(s) ago).
Scanned in 0.9s. drift-scan only reads. It cannot patch, fork, or open a pull request; the full tool proposes a change and a human decides.
The change data ships inside the package, so the scan runs entirely on your machine. Every run prints how old that data is, because a clean result is only as current as the retirements it knew about.
drift-scan is the read-only half. The full tool watches the providers, classifies each change once, patches the call sites it is confident about, verifies the patch mechanically, and opens a draft pull request that a human reads and decides on. Nothing auto-applies. Nothing is ever merged by drift.
Here is one it opened, on a repository the operator does not own: bytedance/MMLongCite #1. It carries the provider's own retirement notice as evidence, states the verification tier the patch reached, and it is a draft — because the decision is the maintainer's, not the tool's.
This list is generated from the extractor's own declarations, not written by hand, so it cannot quietly fall out of step with the code:
Every run also prints its parse coverage per language and discloses the call sites it found but refused to resolve. A clean result should be legible as a real check rather than an empty one.