●PRICE — Gemini 3.6 Flash drops output tokens from $9.00 to $7.50 per million while input holds at $1.50, so verbose workloads feel the difference most●DEFAULT — The Antigravity agent in Managed Agents now runs on 3.6 Flash by default. Setups that never pinned a model may behave differently●VERBOSE — 3.6 Flash answers the developer complaint that 3.5 Flash rambled, tightening token efficiency and agentic planning at the same time●GROK — The Grok 4.1 model family on the Gemini Enterprise Agent Platform is deprecated and shuts down on August 20●CLASSROOM — From August 10, students of all ages in K-12 and higher education can use Gemini in Classroom where admins have granted access, turning materials into flashcards and quizzes●PIXEL — Google's August 12 event is expected to fill in the Pixel 11 hardware details along with AI features across the wider ecosystem●PRICE — Gemini 3.6 Flash drops output tokens from $9.00 to $7.50 per million while input holds at $1.50, so verbose workloads feel the difference most●DEFAULT — The Antigravity agent in Managed Agents now runs on 3.6 Flash by default. Setups that never pinned a model may behave differently●VERBOSE — 3.6 Flash answers the developer complaint that 3.5 Flash rambled, tightening token efficiency and agentic planning at the same time●GROK — The Grok 4.1 model family on the Gemini Enterprise Agent Platform is deprecated and shuts down on August 20●CLASSROOM — From August 10, students of all ages in K-12 and higher education can use Gemini in Classroom where admins have granted access, turning materials into flashcards and quizzes●PIXEL — Google's August 12 event is expected to fill in the Pixel 11 hardware details along with AI features across the wider ecosystem
Green Tests, Dead Production — How Recorded Fixtures Hide a Model Retirement, and a Freshness Gate to Catch It
A test suite that replays recorded API responses will sail straight past a model retirement. I reproduce the failure in a minimal setup and build a cassette freshness gate, with measured overhead.
After reading through the August deprecation cluster in the changelog, I ran my local test suite. Six tests, all green, 0.11 seconds. Under that green sat three recorded responses from a model that stops serving on August 17.
In my solo app project, where I have been evaluating an image generation pipeline, every Gemini API test replays a recorded response — a cassette — instead of hitting the network. That is a reasonable design; nobody wants CI billing them per run. But for one specific failure class, model retirement, the same design actively works against you.
A passing suite and a production system that still works next week are two different claims. I spent a day wiring them back together, and measured what it cost.
What stops in August, and why mocks are structurally blind to it
First, the context. August 2026 is unusually dense with shutdowns.
What
Date
Impact
Imagen 4 family (imagen-4.0-generate-001 / ultra / fast) and the Gemini 3 Image family
Retires 2026-08-17
Image generation pipelines must migrate
Grok 4.1 family on Gemini Enterprise Agent Platform
Retires 2026-08-20
Agent configs need replacement
gemini-robotics-er-1.6-preview
Retires 2026-08-31
Preview-dependent paths need a fallback
3.5 Flash in Gemini Enterprise (global region)
Removed 2026-08-04
The model silently leaves the picker
Dates and scope can shift, so verify against the model deprecation schedule before acting on any of this.
Now consider how most of us test API integrations. Recorded replays, SDK mocks, stub servers — different names for the same move: freezing whatever is on the other side of the network. Latency jitter disappears from your tests. So do rate limits. And so does a model ceasing to exist.
Model retirement fits precisely into the class of failures mocks are designed to hide. The more thoroughly you mock, the later you find out. That inverse relationship is the uncomfortable core of this article.
Reproducing the green-but-dead state in a minimal setup
A working example beats an abstract warning. Here is a minimal client, modeled on a wallpaper app's generation path.
On top of this I wrote six tests — prediction shape, image bytes, aspect ratio, batch output, the empty-predictions case for safety-filtered prompts. On my sandbox VM (Python 3.10.12):
All green. Meanwhile, the recorded model, imagen-4.0-fast-generate-001, retires on August 17. Since the post-retirement behavior cannot be observed yet, I stood up a local stub that mirrors the NOT_FOUND shape returned by previously retired model families, and compared:
imagen-4.0-fast-generate-001: HTTPError 404 Client Error: Not Found (4.1ms)
gemini-3.1-flash-lite-image: OK modelVersion=gemini-3.1-flash-lite-image (2.2ms)
The suite passes in 0.11 seconds; the same code against a live-shaped endpoint dies with a 404. That gap is what "green but dead" means. For what actually happens when production keeps retrying a retired model, I measured it separately in Retired-model retries never show up in your success latency — permanent errors have a way of hiding from healthy-path metrics too.
✦
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
✦A minimal, complete reproduction of the failure mode — six green tests sitting on top of a dependency that retires on August 17, with full client, replay conftest, and cassette code
✦A cassette freshness gate that runs before pytest collection, measured at a median 7.1ms for 200 cassettes (n=50) on a sandbox VM
✦How to choose WARN_DAYS and MAX_AGE_DAYS, and a staged rollout that surfaces real migration work without burying your team in 101 sudden failures
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.
The surprise: cassettes are your most accurate ledger
Before this exercise, I treated cassettes as noise in a dependency audit. When you grep a codebase for model IDs, hits inside test fixtures feel like something to filter out.
The opposite turned out to be true.
Model references in application code hide behind indirection — environment variables, config files, stable aliases. A string scan over source code loses track at every one of those layers. But the modelVersion recorded in a cassette is the resolved model name at the moment the API actually answered. However many layers of indirection your code has, the recording shows the model you really called.
A recorded fixture is simultaneously the device that hides a retirement and the most truthful record of the dependency. Once I saw that dual nature, the plan changed from "mock less" to "read the cassettes as a ledger." The audit procedure itself is the one I described in Before the August 17 image model shutdown, find out where you call it from — the only change is adding your cassette directory to the scan targets, and the quality of the audit improves noticeably.
Implementing the freshness gate — quarantine cassettes before pytest runs
If the cassettes are a ledger, reading them should be automated. This gate scans every cassette at pytest session start and fails on recordings of soon-to-retire models, or recordings that are simply too old.
# tests/freshness_gate.py"""Cassette freshness gate: flag retiring models and stale recordings."""import datetime as dtimport jsonimport pathlib# Source: ai.google.dev/gemini-api/docs/deprecations (checked 2026-08-05)DENYLIST = { "imagen-4.0-generate-001": dt.date(2026, 8, 17), "imagen-4.0-ultra-generate-001": dt.date(2026, 8, 17), "imagen-4.0-fast-generate-001": dt.date(2026, 8, 17), "gemini-robotics-er-1.6-preview": dt.date(2026, 8, 31),}WARN_DAYS = 21 # start failing 21 days before retirementMAX_AGE_DAYS = 90 # recordings older than 90 days demand re-recordingdef scan(cassette_dir: pathlib.Path, today: dt.date | None = None) -> list[str]: today = today or dt.date.today() problems = [] for path in sorted(cassette_dir.glob("*.json")): data = json.loads(path.read_text()) # prefer the resolved model name; fall back to the requested one model = data.get("response", {}).get("modelVersion") \ or data.get("request", {}).get("model", "") retire = DENYLIST.get(model) if retire and (retire - today).days <= WARN_DAYS: problems.append( f"{path.name}: recorded model '{model}' retires {retire.isoformat()} " f"({(retire - today).days} days left)") recorded = data.get("recorded_at") if recorded: age = (today - dt.datetime.fromisoformat(recorded).date()).days if age > MAX_AGE_DAYS: problems.append(f"{path.name}: recording is {age} days old (> {MAX_AGE_DAYS})") return problems
Wiring it into conftest takes a few lines, with an explicit escape hatch for emergencies.
# tests/conftest.py (addition)def pytest_sessionstart(session): import os if os.environ.get("SKIP_FRESHNESS_GATE") == "1": return from tests.freshness_gate import scan problems = scan(CASSETTE_DIR) if problems: import pytest as _pytest _pytest.exit("cassette freshness gate failed:\n " + "\n ".join(problems), returncode=2)
The suite that was all green a moment ago now says this:
! _pytest.outcomes.Exit: cassette freshness gate failed:
imagen_fast_batch.json: recorded model 'imagen-4.0-fast-generate-001'
retires 2026-08-17 (12 days left)
imagen_fast_filtered.json: recorded model 'imagen-4.0-fast-generate-001'
retires 2026-08-17 (12 days left)
imagen_fast_portrait.json: recorded model 'imagen-4.0-fast-generate-001'
retires 2026-08-17 (12 days left)
"12 days left," delivered as a test failure. That is exactly the behavior I wanted: instead of tracking retirement dates on a calendar, the evidence of the dependency — the recording — speaks for itself.
Measured overhead, and how to pick the thresholds
A slow gate gets deleted. I generated 200 cassettes (~2KB each) and timed the scan 50 times on the VM (Python 3.10.12).
Metric
Measured
Cassettes scanned
200
Scan time (median)
7.1ms
Scan time (min–max)
6.5ms – 8.0ms (n=50)
Retiring-model flags
40
Stale-recording flags
101
That is fully buried inside pytest's own startup cost. Performance is not a reason to skip this.
The thresholds deserve more thought than the code. Note the 101 stale flags above: turning MAX_AGE_DAYS on from day one will bury an established suite in failures. My recommended order:
Start with DENYLIST checks only. Those failures are work you genuinely must do, and the failure list doubles as your migration checklist.
Set WARN_DAYS to 21. Migration plus re-recording needs two to three weeks of slack; learning about a retirement on retirement day is not learning, it is triage.
Keep MAX_AGE_DAYS as a warning at first, and promote it to a failure only after re-recording has become routine.
Resisting the urge to enforce everything at once matters. The point of the gate is to build a habit of owning recording freshness — not to hand your team 101 red tests one morning.
Fitting it into daily operation, and the hole that remains
CI integration is essentially done the moment the gate lives in conftest: every ordinary test run doubles as quarantine. When an emergency deploy truly must go through, SKIP_FRESHNESS_GATE=1 exists — and using it should itself be logged.
One honest limitation: DENYLIST is maintained by hand. If you stop reading the changelog, the gate quietly rots. I piggyback the update on my daily reference-data routine, and I deliberately skipped auto-scraping the deprecations page because its structure is not stable enough to trust. The gate reliably communicates the retirements you know about; it does not discover the ones you do not. Keep that boundary in mind.
This week, run scan() once against your own cassette directory. The failure list it prints is your migration to-do list for August 17. Trust your green tests — but interrogate, just once, what the green is standing on. That single interrogation is what makes retirement day a quiet one.
I hope this saves you a scramble. I write this as someone whose own gate said "12 days left" and finally got me moving.
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.