はじめに
きっかけは、Claude Code から Gemini CLI を呼び出して使おうとしたことでした。
最近は Claude Code のようなコーディングエージェントから、別の CLI ツールをサブプロセスとして呼ぶ使い方をよくします。そこで「Claude Code に Gemini CLI を使わせて最新情報を Web 検索させよう」と思い、こんな依頼をしてみました。
「gemini で東京の明日の天気を調べて」
すると Claude Code は内部で gemini -p "..." を実行してくれたのですが、返ってきたのは 「検索できませんでした」というエラーや、ひどいときには 実データではない“それっぽい推測値”(=ハルシネーション)でした。Claude Code 経由だと当然ヘッドレス(-p)実行になるため、ここで初めて「スクリプト実行時だけ Gemini の Web 検索が効かない」問題に気づいたわけです。
手元で切り分けてみると、状況はこう整理できました。
対話モードでは OAuth ログインで普通に使えているのに、
gemini -p "..."でスクリプト(やエージェント)から Google 検索させると、検索結果がいつも空っぽで返ってくる。
調べてみると、これは 認証方式の違いが原因でした。そして「対話は OAuth のまま・コマンドライン実行のときだけ API キーに切り替える」を 同じ gemini コマンドのまま自動でやる仕組みを作れたので、その手順をまとめます。
この記事の設定をしておけば、Claude Code などのエージェントから gemini -p を呼んでも、ちゃんと Web 検索付きの実データが返ってくるようになります。
動作確認バージョン: gemini-cli 0.44.1 / macOS (zsh)
TL;DR(結論)
- Gemini CLI の認証方式は
settings.jsonのsecurity.auth.selectedTypeが最優先。環境変数だけでは上書きできない。 - OAuth 個人アカウント認証だと、環境によっては
google_web_searchが常に空の結果を返すことがある。API キー認証なら Google 検索グラウンディングが正常に動く。 -
GEMINI_CLI_HOMEで設定ホームごと切り替えられることを利用し、-p/--prompt(ヘッドレス実行)のときだけ API キー用ホームに切り替えるラッパー関数を~/.zshrcに仕込む。
gemini() {
local headless=0 a
for a in "$@"; do
case "$a" in
-p|--prompt|--prompt=*) headless=1; break ;;
esac
done
if (( headless )); then
GEMINI_CLI_HOME="$HOME/.gemini-cli-apikey" command gemini --skip-trust "$@"
else
command gemini "$@"
fi
}
これだけで以下のように自動で使い分かれます👇
| 実行方法 | 認証 | Web 検索 |
|---|---|---|
gemini(対話 UI) |
OAuth | — |
gemini -p "..."(CLI) |
API キー | ✅ 動く |
きっかけ:検索結果がすべて「空」になる
gemini -p で天気を調べさせると、こんな返事が返ってきました。
$ gemini -p "東京の明日の天気を google_web_search で調べて"
申し訳ありません。現在Google検索機能から情報を取得できないため、
明日の東京の天気情報を提供することができません。
「ツールが無効なのかな?」と思い、--debug でツールの発火状況を確認すると…
// google_web_search は実際に呼ばれている(status: success)
"response": {
"output": "No search results or information found for query: \"Yahoo Japan\""
}
ツール自体は正常に発火・実行されているのに、検索バックエンドが「Yahoo Japan」のような単純なクエリですら空を返していることが分かりました。
原因:認証方式によって検索グラウンディングの挙動が変わる
切り分けのため、環境変数の GEMINI_API_KEY を使って Gemini API を直接叩いてみます。
curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}" \
-H 'Content-Type: application/json' \
-d '{
"contents":[{"parts":[{"text":"明日の東京の天気を教えて"}]}],
"tools":[{"google_search":{}}]
}'
すると、API キー経由だと検索グラウンディングが正常に動作し、実データが返ってきました。 groundingMetadata(検索結果のソース)もちゃんと付いてきます。
つまり、
- OAuth 個人アカウント認証 → 環境によっては検索が空を返す
- API キー認証 → Google 検索グラウンディングが正常動作
という差があったわけです。
なぜ環境変数だけでは切り替えられないのか
「じゃあ GEMINI_API_KEY を export しておけば API キー認証になるのでは?」と思いますよね。でもダメでした。Gemini CLI のソース(bundle)を読むと、認証方式の決定ロジックはこうなっています。
// 抜粋・簡略化
const authType = settings.merged.security.auth.selectedType;
if (!authType) {
// selectedType が未設定のときだけ、ここで環境変数にフォールバック
// GEMINI_DEFAULT_AUTH_TYPE → GEMINI_API_KEY ...
}
ポイントは、
settings.jsonのsecurity.auth.selectedTypeが最優先で、環境変数(GEMINI_DEFAULT_AUTH_TYPE/GEMINI_API_KEY)は selectedType が未設定のときだけ使われるフォールバックにすぎない
ということ。~/.gemini/settings.json に selectedType: "oauth-personal" がある限り、環境変数では上書きできないのです。
解決策:GEMINI_CLI_HOME で設定ホームごと切り替える
さらにソースを読むと、設定の読み込み先を変えられる環境変数を見つけました。
const baseDir = process.env["GEMINI_CLI_HOME"] || join(os.homedir(), ".gemini");
const settingsPath = join(baseDir, "settings.json");
GEMINI_CLI_HOME を指定すると、そのディレクトリ直下の settings.json を読む=設定ホームをまるごと差し替えられます。
これを使って、
- 対話 UI → デフォルトの
~/.gemini(OAuth) - CLI 実行 → 専用ホーム
~/.gemini-cli-apikey(API キー)
と分ければOK、という作戦です。
設定手順
1. 対話 UI 用(OAuth)はそのまま
~/.gemini/settings.json(初回 gemini 起動で OAuth ログインを選べば自動生成)
{
"security": { "auth": { "selectedType": "oauth-personal" } },
"ide": { "hasSeenNudge": true, "enabled": true }
}
2. CLI 専用の API キー用ホームを作成
mkdir -p ~/.gemini-cli-apikey
cat > ~/.gemini-cli-apikey/settings.json <<'JSON'
{
"security": { "auth": { "selectedType": "gemini-api-key" } },
"ide": { "hasSeenNudge": true, "enabled": false }
}
JSON
このホームには OAuth 資格情報を置かず、認証は環境変数 GEMINI_API_KEY を使います。
3. ~/.zshrc にラッパー関数を追記
# API key (Google AI Studio: https://aistudio.google.com/apikey)
export GEMINI_API_KEY="<あなたのAPIキー>"
# --- Gemini CLI auth split: interactive=OAuth, headless(-p/--prompt)=API key ---
# Interactive UI uses the default ~/.gemini (OAuth login).
# Headless/script runs ("gemini -p ...") switch to a dedicated config home
# (~/.gemini-cli-apikey) whose settings.json forces gemini-api-key auth,
# which has working Google Search grounding via $GEMINI_API_KEY.
gemini() {
local headless=0 a
for a in "$@"; do
case "$a" in
-p|--prompt|--prompt=*) headless=1; break ;;
esac
done
if (( headless )); then
GEMINI_CLI_HOME="$HOME/.gemini-cli-apikey" command gemini --skip-trust "$@"
else
command gemini "$@"
fi
}
反映:
source ~/.zshrc # もしくは新しいターミナルを開く
別ホームに切り替えると ~/.gemini/trustedFolders.json の「信頼フォルダ」情報が参照されず、untrusted directory エラーになります。そのため CLI 実行には --skip-trust を付けています。(代替: export GEMINI_CLI_TRUST_WORKSPACE=true)
動作確認
$ gemini -p "google_web_search を使って東京の明日の天気を調べて。天気・気温・降水確率を簡潔に"
東京の明日の天気は以下の通りです。
* 天気: 雨のち曇
* 最高気温: 22℃
* 最低気温: 18℃
* 降水確率: 100%(夕方以降は30%)
ちゃんと検索付きで実データが返ってきました🎉 対話モード(gemini を引数なしで起動)は、これまで通り OAuth ログインのままです。
ちょっとした工夫ポイント
-
-i/--prompt-interactive(プロンプト実行後に対話を継続するモード)は、あえて切り替え対象から外しています。これは「結局 UI で続ける」ので OAuth が自然なため。 - ラッパーは
command geminiで本体を呼ぶので、無限ループになりません。 - 元に戻したいときは
~/.zshrcのgemini()ブロックを消すだけ。素のコマンドに戻ります。
おわりに
「環境変数で切り替わるはず」という思い込みでハマりましたが、設定の優先順位を実際にソースで確認することで、GEMINI_CLI_HOME というスマートな解決策にたどり着けました。
同じように「Gemini CLI のスクリプト実行で検索が効かない」で困っている方の助けになれば幸いです。
参考
- Gemini CLI 公式: https://geminicli.com/
- Trusted Folders(ヘッドレス実行): https://geminicli.com/docs/cli/trusted-folders/#headless-and-automated-environments
- Google AI Studio(API キー取得): https://aistudio.google.com/apikey
セキュリティ補足:GEMINI_API_KEY を ~/.zshrc に平文で書くのが気になる場合は、macOS キーチェーンや direnv、シークレットマネージャーでの管理を検討してください。