◉GEMINI LABEN
●TTS — Gemini 3.8 Flash TTS と Flash-Lite TTS が GA(9/22)。声の複製に対応するのは Flash だけで、Lite に渡すと 400 が返ります●2.5 — 2.5 系モデルのアクセスは、過去に利用実績のあるユーザーに限定されました(9/18)。廃止ではなく、新規は 3.5 Flash-Lite か 3.8 Flash へ●9/30 — gemini-omni-flash-preview の提供終了まで残り2日、10/2 には gemini-2.5-flash-image も停止。後者の実務上の差し替え先は gemini-3.1-flash-image です●AUTH — 「Workspace の Enterprise アカウントだけ Gemini CLI の認証が通らない」という報告が続いています。個人アカウントとの切り分けが焦点●NEW — 3.8 Flash-Lite TTS への置き換えで、案内音声の声を選び直した記録●429 — 新規プロジェクトが Free tier 表示なのに limit: 0 で 429 が返る。最初の1時間で確かめる点を整理しています●TTS — Gemini 3.8 Flash TTS と Flash-Lite TTS が GA(9/22)。声の複製に対応するのは Flash だけで、Lite に渡すと 400 が返ります●2.5 — 2.5 系モデルのアクセスは、過去に利用実績のあるユーザーに限定されました(9/18)。廃止ではなく、新規は 3.5 Flash-Lite か 3.8 Flash へ●9/30 — gemini-omni-flash-preview の提供終了まで残り2日、10/2 には gemini-2.5-flash-image も停止。後者の実務上の差し替え先は gemini-3.1-flash-image です●AUTH — 「Workspace の Enterprise アカウントだけ Gemini CLI の認証が通らない」という報告が続いています。個人アカウントとの切り分けが焦点●NEW — 3.8 Flash-Lite TTS への置き換えで、案内音声の声を選び直した記録●429 — 新規プロジェクトが Free tier 表示なのに limit: 0 で 429 が返る。最初の1時間で確かめる点を整理しています
記事一覧/開発ツール
⟐ 開発ツール/2026-09-29中級

Gemini CLI に新しいキーを渡しても「API key not valid」。読まれていたのは三か所目の古いキーでした

Google ログインから有料 API キーへ Gemini CLI の認証を切り替えたのに、400 の「API key not valid」が消えませんでした。キーの出どころはシェル・作業ディレクトリの .env・ホームの .env・settings.json の四か所あり、順に照らす小さなスクリプトで原因を一つに絞ります。

gemini-cli6api-key4authentication3troubleshooting58envsettings-jsonheadless

Lab のサイトを深夜に点検する小さなスクリプトを、Google ログインの無料枠から有料の API キーへ切り替えた夜のことです。AI Studio で新しいキーを作り、ターミナルで export し、gemini -p を叩きました。返ってきたのは、切り替える前には見なかった一行でした——API key not valid. Please pass a valid API key.。

キーは作ったばかりです。コピーも確かめました。それでも同じ一行が三度続き、私は「キーが壊れている」と決めつけて AI Studio でもう一本作り直しました。結果は芳しくありませんでした。四本目を作りかけたところで手が止まり、ようやく「CLI はどのキーを読んでいるのか」を疑ったのです。

最初にお伝えしたいのは、結論です。Gemini CLI が読む GEMINI_API_KEY の出どころは一つではなく、私の手元では四か所ありました。 新しいキーを置いたのは一か所目で、CLI が読んでいたのは三か所目——半年前に置いて忘れていた、すでに削除済みの古いキーでした。

四か所のうち、どこが勝つのか

私が手元で確かめた範囲で、GEMINI_API_KEY の候補地は次の四つです。

順場所役割私が忘れていたか
1シェルの環境変数(`export` や `.zshrc`)すでに値があれば、後続の .env は上書きしませんここに新キーを置きました
2作業ディレクトリから上へ辿った `.gemini/.env` または `.env`最初に見つかった一つだけを読みます該当なし
3`~/.gemini/.env`(見つからなければ `~/.env`)2 が無いときの受け皿ここに旧キーが残っていました
4`~/.gemini/settings.json` の認証方式キーを使うか、Google ログインを使うかを決めます切り替え済みでした

CLI は起動時に、1 のシェル環境を先に持ち、2 と 3 の順で .env を探して空いている名前を埋めます。ですから「ターミナルで export したのに旧キーが読まれる」ことは、本来は起きにくいのです。

それが起きた理由は、私のスクリプトが cron から動いていたことにありました。cron の非対話シェルは .zshrc を読みません。1 は空、2 は無し、3 に旧キー。CLI は正しく動き、正しく古いキーを送り、API は正しく 400 を返していたのです。壊れていたのはキーでも CLI でもなく、私の記憶でした。

なお、4 の認証方式は security.auth.selectedType に gemini-api-key oauth-personal vertex-ai のいずれかで保存されます(古い版では selectedAuthType という名前でした)。ここが oauth-personal のままだとキーは読まれもしないので、症状は「API key not valid」ではなく「無料枠の上限」になります。症状の違いが、疑う場所を教えてくれるのだと後から気づきました。

出どころを一枚で照らすスクリプト

同じ夜に書いて、いまも ~/bin に置いてある点検用のスクリプトです。何を解決するかというと、「CLI がどのキーを読むはずか」を推測でなく列挙で答えることです。キーそのものは先頭 6 文字と文字数だけを出し、画面に全文を残しません。

#!/usr/bin/env bash
# where-is-my-gemini-key.sh — GEMINI_API_KEY の候補地を上から順に照らす
mask() { local v="$1"; [ -z "$v" ] && { echo "(未設定)"; return; }; printf '%s… (%d 文字)\n' "${v:0:6}" "${#v}"; }
read_env() { grep '^GEMINI_API_KEY=' "$1" | head -1 | cut -d= -f2- | tr -d '"'"'"' | tr -d '\r'; }
 
echo "[1] シェル環境        : $(mask "${GEMINI_API_KEY:-}")"
 
d="$PWD"; hit=""
while :; do
  for f in "$d/.gemini/.env" "$d/.env"; do
    if [ -f "$f" ] && grep -q '^GEMINI_API_KEY=' "$f"; then hit="$f"; break 2; fi
  done
  [ "$d" = "/" ] && break
  d="$(dirname "$d")"
done
[ -n "$hit" ] && echo "[2] 作業ディレクトリ側 : $hit → $(mask "$(read_env "$hit")")" || echo "[2] 作業ディレクトリ側 : 該当なし"
 
for f in "$HOME/.gemini/.env" "$HOME/.env"; do
  [ -f "$f" ] && grep -q '^GEMINI_API_KEY=' "$f" && echo "[3] ホーム側           : $f → $(mask "$(read_env "$f")")"
done
 
echo "[4] settings.json      : $(python3 - <<'PY'
import json, os
p = os.path.expanduser('~/.gemini/settings.json')
try:
    s = json.load(open(p))
except Exception as e:
    print('読めません:', e); raise SystemExit
print(s.get('security', {}).get('auth', {}).get('selectedType') or s.get('selectedAuthType') or '(未選択)')
PY
)"
 
# [5] CLI を通さず、いま手にしているキーそのものを API に当てる
code=$(curl -s -o /dev/null -w '%{http_code}' -H "x-goog-api-key: ${GEMINI_API_KEY:-}" \
  "https://generativelanguage.googleapis.com/v1beta/models")
echo "[5] キー単体の応答     : HTTP $code(200=有効 / 400=無効なキー / 403=制限か API 無効化)"

なぜこう書いたかを、三つだけ書き残します。第一に、[2] は「最初に見つかった一つで止める」ようにしました。CLI の探索も同じ振る舞いで、上の階層にある二本目の .env は読まれないからです。第二に、tr -d '\r' を入れました。Windows 側で編集した .env の行末に \r が残り、キーの末尾に見えない一文字が付いて 400 になる——これは別の日に踏んだ穴です。第三に、[5] は CLI を経由しません。「キーが悪い」のか「CLI が別のキーを読んでいる」のかを、この一行が分けてくれます。

私の夜の出力は、[1] が未設定、[3] に旧キー、[5] が 400 でした。cron の環境で [1] が空になる事実を、この行を見て初めて受け入れたのです。

同じ一行に辿り着く、別の三つの道

その後、同じ「API key not valid」を別の理由で三度見ています。どれも上のスクリプトで見分けがつきます。

一つ目は、Vertex AI のエクスプレスモードで発行したキーを GEMINI_API_KEY に入れた場合です。あのキーは Vertex 用で、generativelanguage の窓口では無効扱いになります。使うなら GOOGLE_API_KEY に入れ、GOOGLE_GENAI_USE_VERTEXAI=true を添えて認証方式も vertex-ai に切り替える必要があります。[5] が 400 なのに AI Studio で作った覚えがなければ、まずここを疑います。

二つ目は、Cloud コンソールでキーを作り直したあと、古い方を削除したのに .env を直し忘れた場合です。私の夜と同じ型で、[3] に旧キー、[5] が 400 になります。キーのローテーションは「新しい方を置く」までで安心してしまい、「古い方を消す」場所が一つ多かったのです。

三つ目は、export GEMINI_API_KEY="…" を .zshrc に書いたとき、閉じ引用符の前に改行や空白が紛れた場合です。[1] の文字数が AI Studio のキー(AIza で始まる 39 文字)より一文字多ければ、それが答えです。目では見えないので、文字数だけを疑う習慣にしています。同じ一文字は、皆さまの .zshrc にも紛れているかもしれません。

一方で、[5] が 403 のときはキーは本物です。Cloud コンソール側で API の制限がかかっているか、Generative Language API がそのプロジェクトで無効になっているかで、直す場所が CLI から離れます。400 と 403 を同じ「認証エラー」として扱っていたころは、この分岐で毎回遠回りをしておりました。

私が引いた線引き

この夜を境に、キーの置き場所について一つの線を引きました。キーは一か所にだけ置き、残りの三か所は「無いこと」を点検の対象にします。 具体的には ~/.gemini/.env を唯一の置き場に決め、シェルの export も作業ディレクトリの .env も持たないことにしました。cron からでもターミナルからでも同じ一か所が読まれる——探索の順番を覚える代わりに、順番が意味を持たない配置を選んだのです。

非対話で動かす場面では、もう一つ添えています。GEMINI_DEFAULT_AUTH_TYPE=gemini-api-key を cron の環境に置き、認証方式の選択が settings.json の状態に依存しないようにしました。誰かの手で /auth から Google ログインに戻されていても、夜間の実行は影響を受けません。

同じ考え方で、MCP サーバーの有効・無効も設定の置き場所を一つに寄せた経緯を つないだ MCP を初めて外した日 — 常時有効にするツールを選ぶ基準 に書いております。置き場所が散ると、道具は正しく動いたまま、私の側が間違えるのです。

次にキーを差し替える日のために

差し替えの前に、上のスクリプトを一度だけ走らせて [1]〜[3] が空、または旧キー一本だけであることを確かめます。差し替えたら、[5] が 200 になるのを見てから CLI に戻ります。手順はそれだけで、私の場合は三本のキーを無駄にした夜が一度で済むようになりました。

同じ名前のキーが三つの経路で取り合う話は、CLI に限らず SDK を使ったコードでも起きます。読み込み順を入れ替えても勝者が変わらなかった顛末と、供給元から台帳を作る手順は Gemini の API キーを差し替えても旧キーが勝つ — 決め手は読み込み順ではありませんでした に、動くコードとともに残しております。CLI の四か所を片づけたあと、コード側の三経路まで揃えたい方に届けば幸いです。

まずは ~/.gemini/.env を cat して、そこに何が残っているかを見るところから始めていただければと思います。私はそこに、半年前の自分を見つけました。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⟐ 開発ツール2026-09-15
承認ルールを書いたのに一度も一致していませんでした — policy を点検して回った記録
Gemini CLI の policy に書いた承認ルールが一度も一致していませんでした。照合対象が JSON 文字列だったこと、置き場所の階層が無効だったこと、無人実行で ask_user が deny になること。点検を機械化したスクリプトまで書き残します。
⟐ 開発ツール2026-05-23
LM Studio で Gemma 4 MLX が Failed to load model になる時の切り分けと修正
LM Studio で mlx-community/gemma-4-26b-a4b-it-4bit を読み込もうとして「Failed to load model」と返される現象を、M シリーズ Mac での実機検証で見つけた 4 系統の原因と、それぞれの確認・修正手順に整理しました。
⟐ 開発ツール2026-05-11
Gemini CLIをiOSアプリ開発に1週間使い続けて分かったこと
個人でiOS/Androidアプリを開発してきた経験から、Gemini CLIをiOS開発ワークフローに1週間組み込んで分かった実態を報告します。翻訳補助・リリースノート生成・コードレビューでの具体的な使い方と、Claude Codeとの使い分け方も正直に書きました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます