Python CI guard: fail the build on retired Veo, Imagen, Sora ids
Twenty-three lines of standard-library Python scan a repo for Veo 2/3, Imagen 4 and sora-2 ids with their shutdown dates, and exit 1 when one is found.

Run a script that matches retired model ids with word-boundary rules, prints file, line and shutdown date, and exits 1 on any hit. Google and OpenAI each published shutdown dates this year, and a stale id in a config file fails at runtime, not at review, so the cheapest fix is a CI step.
The ids and their dates
The map comes from the two vendor pages. Google lists the Veo and Imagen ids under separate shutdown dates, and the three Veo 3.1 preview ids go on October 22, which has not happened yet, so a hit on them today is a warning about a deadline, not an outage.
| Ids | Vendor | Shutdown |
|---|---|---|
| veo-2.0-generate-001, veo-3.0-generate-001, veo-3.0-fast-generate-001 | 2026-06-30 | |
| imagen-4.0-generate-001, imagen-4.0-ultra-generate-001, imagen-4.0-fast-generate-001 | 2026-08-17 | |
| veo-3.1-generate-preview, veo-3.1-fast-generate-preview, veo-3.1-lite-generate-preview | 2026-10-22 | |
| sora-2, sora-2-pro and three dated snapshots | OpenAI | 2026-09-24 |
The script
Save it as guard.py and run python3 guard.py . in the pipeline. Longer ids are tried first and the lookarounds reject a longer name, so sora-2 does not fire inside sora-2-pro-2025-10-06, which has its own entry.
import pathlib
import re
import sys
RETIRED = { # id -> shutdown date from the vendors' pages (read 2026-10-07)
"veo-2.0-generate-001": "2026-06-30", "veo-3.0-generate-001": "2026-06-30",
"veo-3.0-fast-generate-001": "2026-06-30", "imagen-4.0-generate-001": "2026-08-17",
"imagen-4.0-ultra-generate-001": "2026-08-17", "imagen-4.0-fast-generate-001": "2026-08-17",
"veo-3.1-generate-preview": "2026-10-22", "veo-3.1-fast-generate-preview": "2026-10-22",
"veo-3.1-lite-generate-preview": "2026-10-22", "sora-2": "2026-09-24",
"sora-2-pro": "2026-09-24", "sora-2-2025-10-06": "2026-09-24",
"sora-2-2025-12-08": "2026-09-24", "sora-2-pro-2025-10-06": "2026-09-24",
}
ids = sorted(RETIRED, key=len, reverse=True)
PATTERN = re.compile(r"(?<![\w.-])(" + "|".join(map(re.escape, ids)) + r")(?![\w.-])")
hits = []
for path in pathlib.Path(sys.argv[1]).rglob("*"):
if path.suffix in {".py", ".ts", ".js", ".json", ".yaml", ".yml", ".env"}:
for n, line in enumerate(path.read_text(errors="ignore").splitlines(), 1):
hits += [f"{path}:{n}: {m[1]} (shut down {RETIRED[m[1]]})" for m in PATTERN.finditer(line)]
print("\n".join(hits))
sys.exit(1 if hits else 0)What it cannot see
It reads text files with a handful of extensions and knows only the ids in its table. It misses ids assembled at runtime, ids in a database, and ids in a file type you did not list. Treat a clean run as no known stale strings, not as proof of a migration.
For the replacement side, Sume's GET /v1/videos/models and GET /v1/images/models list what the catalog accepts today. The guard never calls them, so it cannot tell you a new id is valid.
Sources
Related posts
More in Developers
- Python guard: refuse a Short over 180 s before the render bills
A 20-line Python check that blocks a Shorts render over 180 seconds and prices the Sume Timeline minutes (10 cents each) before you submit.
- Python: cheapest Sume image model that lists your aspect ratio
A 25-line Python script reads Sume's image catalog, keeps models that list your aspect ratio, prices each from its endpoints record and prints the cheapest.
- Python preflight for a Sume bulk body: count, keys, bytes, caps
Check a Sume bulk-run body offline before the POST: 1 to 100 items, concurrency 1 to 16, 64 input keys, 2 MiB input, 4 MiB body and a spend cap in range.
- Sume job webhook receiver in plain Python, no framework
A 30-line stdlib http.server receiver for Sume job webhooks: raw-body HMAC, 300-second window, rotation-safe header, refuses an empty secret, answers 204.
Written by Sume