◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 7 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 7 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-09-01Advanced

Your New Gemini API Key Never Took Effect, and Load Order Wasn't the Reason

When several sources supply the same environment variable, swapping your key can leave the old value in place. Here is what four load orders actually produced, and a ledger that records which source won.

Gemini API249API keysenvironment variablesmigration10operations19

✦ Premium Article

September arrived, and with it the one item in my Gemini code that has to be finished this month: moving from standard API keys to auth keys. The clearest place to start was the wallpaper category classification batch I run as an indie developer, so I started there.

I rewrote the value in .env to the new key, ran the job locally once, confirmed a response came back, and put it back on its schedule. The next morning's log showed requests still going out with the old key.

The .env file had definitely changed. The process was definitely reading that file. The old value was being used anyway.

Three Sources Were Fighting Over One Name

Before hunting for a cause, I listed every place that hands GEMINI_API_KEY to that process. There were three.

Supply pathWhere it livesFindable by code search?
Shell environmentAn export in the scheduler's wrapper scriptYes, but in a different repo
dotenv file.env at the project rootFilename yes, value no
Secret fileJSON placed at deploy timeNo

This is where the first mistake happens. Running grep -rn "GEMINI_API_KEY" surfaces the places that read the value: the os.environ.get(...) and process.env.... lines. The places that write it live in CI settings screens, files expanded at deploy time, and wrapper scripts, which sit either outside the repository or in a corner of it.

Count only the readers and you conclude "three reads, three fixes." The entire supply-side inventory falls out of scope. I got that order wrong once.

Four Load Orders, Same Winner

My first suspicion was load order. If the last read wins, I reasoned, I just need the new key read last. It is a reasonable guess.

So I wrote the smallest possible loader with three supply paths, and ran it with the order shuffled.

# probe.py — a config loader with three supply paths; shuffle the order, watch the winner
import os, sys, json
from dotenv import load_dotenv
 
def from_shell():
    # only reads what is already in the process; writes nothing
    return os.environ.get("GEMINI_API_KEY")
 
def from_dotenv():
    # python-dotenv defaults to override=False: it will not write over an existing value
    load_dotenv("/tmp/keyaudit/.env")
    return os.environ.get("GEMINI_API_KEY")
 
def from_secretfile():
    # a plain assignment; overwrites unconditionally
    d = json.load(open("/tmp/keyaudit/secrets.json"))
    os.environ["GEMINI_API_KEY"] = d["GEMINI_API_KEY"]
    return os.environ["GEMINI_API_KEY"]
 
fns = {"shell": from_shell, "dotenv": from_dotenv, "secretfile": from_secretfile}
for name in sys.argv[1].split(","):
    fns[name]()
 
print(f"order={sys.argv[1]:32s} winner={os.environ.get('GEMINI_API_KEY')}")

With the new key in .env, the old key in the secret file, and the old key in the shell, I tried four orders:

order=shell,dotenv,secretfile          winner=OLD_STANDARD_KEY_from_secretfile
order=shell,secretfile,dotenv          winner=OLD_STANDARD_KEY_from_secretfile
order=dotenv,secretfile,shell          winner=OLD_STANDARD_KEY_from_secretfile
order=secretfile,dotenv,shell          winner=OLD_STANDARD_KEY_from_secretfile

All four produced the same winner: the secret file holding the old key. Rearranging the sequence moved nothing.

The reason is that each path has its own policy toward an existing value. load_dotenv() respects what is already there and declines to write. A plain os.environ[...] = value overwrites without looking. So the outcome is not decided by who reads last. It is decided by this: if any path grants itself permission to overwrite, that path becomes the final winner.

Until those four lines printed, I had been shuffling import statements. While you believe the cause is ordering, no amount of reordering changes the symptom, so you gather no new information.

✦

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
✦You will be able to tell, before you start a key migration, which of your supply paths are allowed to overwrite and which ones fail silently
✦You will be able to drop a ledger into your own runtime that records which key actually reached the request, without ever writing the key value to a log
✦You will be able to explain why shuffling four different load orders changed nothing, and skip the detour of rearranging import statements to fix it
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 $10 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-08-01
When Sampling Parameters Were Deprecated, Diversity Broke Before Determinism Did
Deprecating temperature, top_p and top_k hurt the diversity-generating side of my pipeline, not the deterministic side. Counting real call sites, moving diversity to the input layer, and measuring effective diversity.
◈ API / SDK2026-10-14
When Your config Raises "Extra inputs are not permitted", Check the Spelling of Every Key
If a google-genai call fails with a pydantic ValidationError (extra_forbidden) the moment you pass a config, it is almost always a one-letter typo. Here is the output I reproduced, a small function that checks keys before the call, and how to tell why response.text came back as None.
◈ API / SDK2026-10-08
When a Deprecation Has No Shutdown Date: Re-Picking an Image Model on Three Conditions
gemini-nano-banana-2.1 went GA on October 6, and gemini-3.1-flash-image was deprecated with no shutdown date. Here is a table and a short script for choosing a replacement by resolution, reference images, and search grounding.