0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini CLIで「対話はOAuth・スクリプトはAPIキー」を自動で使い分ける

0
Last updated at Posted at 2026-06-02

はじめに

きっかけは、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.jsonsecurity.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.jsonsecurity.auth.selectedType が最優先で、環境変数(GEMINI_DEFAULT_AUTH_TYPE / GEMINI_API_KEY)は selectedType が未設定のときだけ使われるフォールバックにすぎない

ということ。~/.gemini/settings.jsonselectedType: "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 で本体を呼ぶので、無限ループになりません。
  • 元に戻したいときは ~/.zshrcgemini() ブロックを消すだけ。素のコマンドに戻ります。

おわりに

「環境変数で切り替わるはず」という思い込みでハマりましたが、設定の優先順位を実際にソースで確認することで、GEMINI_CLI_HOME というスマートな解決策にたどり着けました。

同じように「Gemini CLI のスクリプト実行で検索が効かない」で困っている方の助けになれば幸いです。

参考

セキュリティ補足:GEMINI_API_KEY~/.zshrc に平文で書くのが気になる場合は、macOS キーチェーンや direnv、シークレットマネージャーでの管理を検討してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?