●CLI — As of Jun 18, Gemini CLI and the Gemini Code Assist IDE extensions stop serving AI Pro/Ultra and free individual users; Antigravity CLI is the successor●FLASH — The Gemini 3.5 series begins with 3.5 Flash, built for agents and coding with strength on long-horizon tasks●DEEPTHINK — Gemini 3 Deep Think is rolling out to Google AI Ultra as the top reasoning mode for math, science, and logic●APP — The Gemini app gains a Daily Brief, a redesigned interface, the Gemini Omni video model, and a personal agent called Gemini Spark●DESIGN — A new design language, Neural Expressive, rebuilds the experience for richer visuals and faster switching between modalities●ULTRA — Google AI Ultra bundles top model access, Deep Research, Veo 3 video, and a 1M-token context window●CLI — As of Jun 18, Gemini CLI and the Gemini Code Assist IDE extensions stop serving AI Pro/Ultra and free individual users; Antigravity CLI is the successor●FLASH — The Gemini 3.5 series begins with 3.5 Flash, built for agents and coding with strength on long-horizon tasks●DEEPTHINK — Gemini 3 Deep Think is rolling out to Google AI Ultra as the top reasoning mode for math, science, and logic●APP — The Gemini app gains a Daily Brief, a redesigned interface, the Gemini Omni video model, and a personal agent called Gemini Spark●DESIGN — A new design language, Neural Expressive, rebuilds the experience for richer visuals and faster switching between modalities●ULTRA — Google AI Ultra bundles top model access, Deep Research, Veo 3 video, and a 1M-token context window
Catching Deprecated Gemini Models in CI ― A Guard for Back-to-Back Shutdown Deadlines
When shutdowns and deprecations pile up, build a CI check that mechanically finds stale Gemini model strings across your repo. Includes a deprecation registry, a scanner, and a days-remaining warn/fail tier you can copy and run.
Last week I found an embarrassing miss in my own code. A thumbnail-generation script for one of my wallpaper apps ― one that runs only once a month ― still pinned an old image model I was sure I had migrated away from. The code I touch regularly was already on the new model, but a one-off script that never goes through CI sat unseen, quietly waiting to return 404s the moment its shutdown date passed.
June is a stretch where Gemini shutdowns land one after another. The personal tier of the CLI and Code Assist stops accepting requests, and two preview image models are about to be turned off. The migration guides are written carefully, but a guide tells you what is shutting down, not which line of which of your files is about to break. As long as you leave the latter to human memory and ad-hoc grep, references buried in code you rarely open will slip through.
I run four technical blogs and a handful of my own iOS and Android apps, all solo. Each one pins a slightly different model, so manually checking every repo on each shutdown isn't realistic. So I built a small mechanism that fails fast in CI on stale model strings. This article shares that deprecation registry and scanner in a form that actually runs.
Why "just read the migration guide" isn't enough
When you migrate by hand, three things usually leak through.
First, shutdown dates are scattered. When something dies on the 18th, something else on the 25th, and a third next month, a human only keeps the nearest deadline in mind. The rest get remembered on the day they arrive.
Second, references are scattered. You migrate the active app itself, but old model IDs linger in doc code samples, past examples, ops scripts that never hit CI, and comments in .env.example. None of these pass your normal tests, so you learn they broke from a user report or a crash log.
Third, a model ID is just a string. No type, no schema ― neither the compiler nor the linter says a word. "gemini-3.1-flash-image-preview" can sit in your code, and nothing in that code records that it disappears in eight days.
All three are exactly the kind of work machines are good at and humans are bad at. So let the machine do it.
Start with a single "deprecation registry" file
The first step is to gather the canonical list of shutting-down models into one JSON file. This becomes the single source of truth. Each entry ties together the model ID, its shutdown date, its replacement, and the source.
{ "$schema_note": "Canonical list of Gemini models scheduled for shutdown. Dates are YYYY-MM-DD (judged conservatively against UTC).", "deprecations": [ { "model": "gemini-3.1-flash-image-preview", "shutdown": "2026-06-25", "replacement": "gemini-3.1-flash-image", "note": "Preview image generation. Move to GA.", "source": "ai.google.dev/gemini-api/docs/deprecations" }, { "model": "gemini-3-pro-image-preview", "shutdown": "2026-06-25", "replacement": "gemini-3.1-pro-image", "note": "Preview image generation. Move to GA.", "source": "ai.google.dev/gemini-api/docs/deprecations" }, { "model": "gemini-2.0-flash", "shutdown": "2026-09-30", "replacement": "gemini-3.5-flash", "note": "Default progressively moving to 3.5 Flash.", "source": "ai.google.dev/gemini-api/docs/changelog" } ]}
The key here is to always attach a shutdown date. It's the raw material for later varying the warning's strength by days remaining. For a model whose shutdown date isn't known yet, put in a tentatively close date and lean conservative. Treat the official deprecations page as the source of truth, and when a new shutdown is announced, update only this file. Never hard-code dates in the script.
Ideally you don't copy this registry into each repo ― you place one in a shared location and reference it. I share a single file across all four sites. When a new shutdown shows up, fixing one place makes every repo's verdict current at once.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Catch the old model string sitting inside a script you almost never open ― the kind of reference reading the migration guide never surfaces
✦Get a copy-and-run deprecation registry plus a scanner that flips between warn and fail based on how many days remain until shutdown
✦Even with several repos and several shutdown dates in flight, reach a state where you can see exactly where each landmine is and how many days are left
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Next, write a script that loads the registry, walks the whole repo, and reports every line where an old model ID appears. It's pure Python.
#!/usr/bin/env python3"""Detect deprecated Gemini model strings in a repository.Switches between warning (informational) and error (fails CI)based on the days remaining until shutdown."""import jsonimport osimport reimport sysfrom datetime import date, datetime# Treat as error (fail CI) once remaining days drop to or below thisFAIL_WITHIN_DAYS = 30# Extensions to scanSCAN_EXT = {".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".json", ".yaml", ".yml", ".md", ".mdx", ".env", ".sh"}# Directories to skipSKIP_DIRS = {".git", "node_modules", ".next", "dist", "build", "vendor", ".venv"}def load_registry(path): with open(path, encoding="utf-8") as f: data = json.load(f) items = [] for d in data["deprecations"]: shutdown = datetime.strptime(d["shutdown"], "%Y-%m-%d").date() # Bound by non-word characters so 'gemini-2.0-flash' does not # falsely match 'gemini-2.0-flash-lite' pattern = re.compile(r"(?<![\w.-])" + re.escape(d["model"]) + r"(?![\w-])") items.append({**d, "shutdown_date": shutdown, "pattern": pattern}) return itemsdef iter_files(root): for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] for name in filenames: ext = os.path.splitext(name)[1].lower() if ext in SCAN_EXT or name.startswith(".env"): yield os.path.join(dirpath, name)def scan(root, registry, today): hits = [] for path in iter_files(root): try: with open(path, encoding="utf-8", errors="ignore") as f: lines = f.readlines() except OSError: continue for lineno, line in enumerate(lines, 1): for item in registry: if item["pattern"].search(line): remaining = (item["shutdown_date"] - today).days hits.append({ "path": os.path.relpath(path, root), "line": lineno, "model": item["model"], "replacement": item["replacement"], "remaining": remaining, "text": line.strip()[:120], }) return hitsdef main(): registry_path = os.environ.get("DEPRECATION_REGISTRY", "deprecations.json") root = sys.argv[1] if len(sys.argv) > 1 else "." today = date.today() registry = load_registry(registry_path) hits = scan(root, registry, today) if not hits: print("OK: no deprecated model strings found.") return 0 # Sort by fewest days remaining first hits.sort(key=lambda h: h["remaining"]) has_error = False for h in hits: level = "ERROR" if h["remaining"] <= FAIL_WITHIN_DAYS else "WARN" if level == "ERROR": has_error = True when = f"{h['remaining']}d left" if h["remaining"] >= 0 else f"{abs(h['remaining'])}d overdue" print(f"[{level}] {h['path']}:{h['line']} " f"{h['model']} -> {h['replacement']} ({when})") print(f" {h['text']}") print(f"\n{len(hits)} hit(s) / error threshold within {FAIL_WITHIN_DAYS} days") return 1 if has_error else 0if __name__ == "__main__": sys.exit(main())
This is where putting shutdown dates in the registry pays off. The one with 8 days left fails CI as an ERROR, while the one with 105 days stays a WARN that only nudges you. Not failing on everything uniformly is deliberate ― you don't want to halt development for a migration that still has months of runway. As the deadline approaches, the same reference is automatically promoted from WARN to ERROR.
The word-boundary trap ― the heart of the implementation
The thing I was most careful about is the regex boundary. A naive "gemini-2.0-flash" in line drags in the still-live gemini-2.0-flash-lite and produces false positives. Relying on \b alone, on the other hand, leaves the handling of the periods and hyphens inside a model ID up to each engine's quirks, so it won't stop where you expect.
So I bound both sides to a position that is "not a word character, period, or hyphen."
# before: preceding char is none of \w . - / after: following char is none of \w -pattern = re.compile(r"(?<![\w.-])" + re.escape(d["model"]) + r"(?![\w-])")
Don't forget to always run re.escape. Model IDs contain periods, so without escaping, the . acts as "any single character" ― another source of false matches. I forgot this at first and watched it match non-existent strings like gemini-3x1-flash, which left me scratching my head.
Driving false positives close to zero matters more than the feature itself for this kind of tool. The moment it cries wolf, the team stops reading its output, and the tool may as well not exist.
Wire it into CI
After that, you just call it from CI. With GitHub Actions it's a few lines.
If you want to catch it locally first, the same script slots into a pre-commit local hook.
# .pre-commit-config.yaml- repo: local hooks: - id: gemini-deprecation-guard name: Gemini deprecated-model guard entry: python ci/check_deprecated_models.py language: system pass_filenames: false
I put it in both ― CI as the last line of defense, pre-commit as the everyday nudge. Pre-commit surfaces even the still-lenient WARNs locally, so you naturally fix them while the deadline is still far off.
Semi-automate the registry updates
If you maintain the registry purely by hand, that's exactly where new shutdown information slips. So I keep a companion script that cross-references the Gemini API model list and surfaces models that carry a deprecation hint on the API side but aren't yet in the registry.
import osfrom google import genaiclient = genai.Client(api_key=os.environ["YOUR_GEMINI_API_KEY"])# Fetch the model list the API returns and pick up ones whose# description hints at deprecationfor m in client.models.list(): name = m.name.split("/")[-1] desc = (getattr(m, "description", "") or "").lower() if "deprecat" in desc or "preview" in name: print(f"check: {name} ― {desc[:80]}")
This isn't meant to rewrite the registry automatically; it's a daily-batch heads-up about "models a human should go check on the official deprecations page." The final shutdown date and replacement are always pinned to the official docs. Deciding shutdown dates mechanically from API metadata alone risks failing CI on a wrong date.
A few things I noticed after running it for a while.
Code that builds a model ID by concatenating strings won't be caught by this scan. A form like "gemini-3.1-" + variant is a blind spot for static checks. As a countermeasure, I made it a rule to consolidate model IDs into a single constants file and reference that constant everywhere. With the scan target narrowed to one constants file, the blind spot disappears.
Old model IDs inside docs and blog posts get hit without mercy. That's correct behavior. Better to fix the article than to have readers copy something that no longer works. That said, for spots you decide to keep as a historical record, add a marker like # deprecation-guard: allow at the end of the line to allow skipping. My own rule is to keep the allowlist small and always leave a comment explaining why it's there.
The shutdown-date judgment shifts by a day depending on whether you cut on UTC or local time. I lean conservative and give the threshold a little slack so a reference is promoted to ERROR the day before the UTC shutdown. Fixing things a day early beats crying over a timezone right at the wire ― far healthier for the mind, too.
Run it on one repo first
The mechanism is small, so there's no need to roll it out across every repo at once. Write just one near-term shutdown into the registry, and run python check_deprecated_models.py . once at the root of the repo you're most nervous about migrating. If even a single reference you'd forgotten surfaces, this mechanism has already paid for itself. From there, growing the scanned extensions and the registry bit by bit is plenty.
When I first ran it myself, that opening one-off script got caught, and only then could I meet the shutdown date with peace of mind. As a small investment to turn a deadline-chased migration into one that isn't, I hope it helps anyone facing the same problem.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.