GEMINI LABJP
0.60.0 — Nearly every line of this Gemini CLI release tightens a boundary around extensions and MCP. An extension that changes your environment now has to ask first09/30 — Twelve days until gemini-omni-flash-preview shuts down. The successor is gemini-omni-1.1-flash, and the work starts with an inventory of your call sitesLICENSE — Two threads are still collecting reports of accounts set up exactly as documented being turned away one day with "you do not have a valid license of this product"NEW — When a Gem never appears in someone else's list, there are three places it can be stuck. Decide the order you check them inPDF — When a document is refused for being too large, deciding what to cut usually beats deciding where to splitADC — Antigravity's enterprise accounts can now pick Gemini 3.8 Flash through ADC. The same model is governed differently depending on the door you come in by0.60.0 — Nearly every line of this Gemini CLI release tightens a boundary around extensions and MCP. An extension that changes your environment now has to ask first09/30 — Twelve days until gemini-omni-flash-preview shuts down. The successor is gemini-omni-1.1-flash, and the work starts with an inventory of your call sitesLICENSE — Two threads are still collecting reports of accounts set up exactly as documented being turned away one day with "you do not have a valid license of this product"NEW — When a Gem never appears in someone else's list, there are three places it can be stuck. Decide the order you check them inPDF — When a document is refused for being too large, deciding what to cut usually beats deciding where to splitADC — Antigravity's enterprise accounts can now pick Gemini 3.8 Flash through ADC. The same model is governed differently depending on the door you come in by
Articles/API / SDK
API / SDK/2026-08-04Advanced

Retrying a Retired Model Never Showed Up in Success Latency

With the image generation models shutting down soon, I rebuilt a mock server to see what my retry layer actually does when it hits a retired model ID. The damage landed in wall time and queue wait, and never touched the metric I was watching.

Gemini API240Error Handling3Retry Design2Model Migration7Observability4

Premium Article

August 4 was the day Gemini 3.5 Flash dropped off the model list in the global region of the Gemini Enterprise app. On August 17, the older image generation models shut down. I opened my config files to count how many places a model ID was hard-coded, and then stopped.

Counting was not the useful part.

I could not actually describe how my code would break the day after a shutdown. Calls would fail, sure. But what my retry layer would do with those failures, and what that would cost the batch as a whole, was pure guesswork.

Planning a migration on guesswork felt uncomfortable, so I reproduced the failure locally and measured it.

Permanent and Transient Errors Were Sitting in the Same Bucket

Reading back through the retry layer of a scheduled batch I keep running as an indie developer, one thing was immediately obvious.

On failure, retry with exponential backoff, up to four attempts. That was the entire policy. Nothing looked at why the call failed.

For overload (503 UNAVAILABLE) or rate limits (429 RESOURCE_EXHAUSTED), that policy is correct. Those are errors that time fixes.

A retired model is not. Wait 3.5 seconds and call again, and you get the same 404 NOT_FOUND back.

So for every model ID I failed to migrate, my batch would throw four calls it was guaranteed to fail, sleeping 3.5 seconds along the way.

The part I could not size was the actual damage. Was this "somewhat slower," or was it serious?

The Harness: Reproducing a Retired Model's Response Locally

Hammering the live API with retired model IDs is not an option, so I stood up a mock server that returns the error shapes documented in the official docs — 404 NOT_FOUND for a retired model, 503 UNAVAILABLE for overload.

Retired IDs always get a 404. Live models return 503 six percent of the time, and otherwise a 200 after a simulated 40ms of generation latency.

# server.py - a mock that reproduces retired-model responses locally
import json, random, threading, time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
 
RETIRED = {"imagen-4.0-generate-001", "gemini-3-image-preview"}
AVAILABLE = {"gemini-3.6-flash", "nano-banana-2-lite"}
rng = random.Random(20260804)   # fixed seed for reproducibility
COUNT = {"total": 0, "retired": 0, "transient": 0, "ok": 0}
LOCK = threading.Lock()
 
class H(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"   # keep-alive; without this the measurement drowns in TCP setup
    def log_message(self, *a): pass
 
    def do_POST(self):
        n = int(self.headers.get("content-length", 0))
        model = json.loads(self.rfile.read(n) or b"{}").get("model", "")
        with LOCK:
            COUNT["total"] += 1
 
        if model in RETIRED:
            with LOCK:
                COUNT["retired"] += 1
            self._send(404, {"error": {"code": 404, "status": "NOT_FOUND",
                "message": f"models/{model} is not found or no longer supported."}})
            return
 
        if rng.random() < 0.06:      # transient overload
            with LOCK:
                COUNT["transient"] += 1
            self._send(503, {"error": {"code": 503, "status": "UNAVAILABLE",
                "message": "The model is overloaded. Please try again later."}})
            return
 
        time.sleep(0.04)             # simulated generation latency
        with LOCK:
            COUNT["ok"] += 1
        self._send(200, {"model": model, "output": "ok"})
 
    def do_GET(self):
        if self.path == "/models":
            self._send(200, {"models": [{"name": f"models/{m}"} for m in sorted(AVAILABLE)]})
        else:
            self._send(404, {"error": {"code": 404, "status": "NOT_FOUND"}})
 
    def _send(self, code, obj):
        b = json.dumps(obj).encode()
        self.send_response(code)
        self.send_header("content-type", "application/json")
        self.send_header("content-length", str(len(b)))
        self.end_headers()
        self.wfile.write(b)
 
ThreadingHTTPServer(("127.0.0.1", 8931), H).serve_forever()

One note on protocol_version. Left at the default HTTP/1.0, every call re-establishes a TCP connection, and the difference you are trying to measure disappears into connection setup. My first run produced almost no separation between the three clients, and this was why.

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
Measured impact of retrying permanent errors: batch wall time went from 1.56s to 8.36s under identical conditions (median of 3 runs)
The metric that actually moves: not success-latency p95, but queue wait p95 from submission to execution start (759ms vs 4,682ms)
A preflight check that drops retired model IDs at one boundary, and why you should not adopt it for speed
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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

API / SDK2026-08-19
gemini-2.5-flash Can Return a 404 'no longer available' While the Docs Still List No Shutdown Date
The shutdown date for gemini-2.5-flash is blank in the official deprecation table, yet 404 reports have been circulating since July. Here is why reading that column as a safety margin misleads you, and how to check your own keys.
API / SDK2026-08-17
After generated_images Disappeared: Three Branches Between the Response and a Saved File
Once you switch to generate_content, the line that breaks is usually the save. Here are the three branches on the response side - zero image parts, a shifting image count, and picking the extension - plus a receiver function you can drop in.
API / SDK2026-08-14
Rewriting generate_images as generate_content, where the arguments actually go
Ahead of the August 17 Imagen shutdown, I checked the SDK directly: of the 17 arguments in GenerateImagesConfig, only 5 move across unchanged. Here is the mapping, a compatibility layer that keeps callers working, and a way to verify the request shape without an API key.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links