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.

4 min readSume
All posts

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.

Retired or retiring ids in the guard (vendor deprecation pages, read 2026-10-07)
IdsVendorShutdown
veo-2.0-generate-001, veo-3.0-generate-001, veo-3.0-fast-generate-001Google2026-06-30
imagen-4.0-generate-001, imagen-4.0-ultra-generate-001, imagen-4.0-fast-generate-001Google2026-08-17
veo-3.1-generate-preview, veo-3.1-fast-generate-preview, veo-3.1-lite-generate-previewGoogle2026-10-22
sora-2, sora-2-pro and three dated snapshotsOpenAI2026-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

All Developers posts

Written by Sume