◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — 旧 Deep Research エージェントは 10/23 に停止済み。呼び出しが失敗する場合は deep-research-preview-04-2026 へ●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●CLI — Gemini API CLI(gemini-api)がベータで登場(10/27)。エージェントやCI向けのゼロ依存クライアントです●STORE — 新規の有料プロジェクトでは生成リクエストの保存(store)が既定でオンになります(10/26)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — 旧 Deep Research エージェントは 10/23 に停止済み。呼び出しが失敗する場合は deep-research-preview-04-2026 へ●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●CLI — Gemini API CLI(gemini-api)がベータで登場(10/27)。エージェントやCI向けのゼロ依存クライアントです●STORE — 新規の有料プロジェクトでは生成リクエストの保存(store)が既定でオンになります(10/26)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/開発ツール
⟐ 開発ツール/2026-10-29中級

夜間バッチが朝まで止まっていた — gemini-api の終了コードで「再試行してよい失敗」を分ける

10/27 にベータで出た gemini-api CLI は、終了コードが 0・1・2・3 の四つ。どれを再試行し、どれで夜間バッチ全体を止めるか。bash のラッパーと、再実行で二重に課金される落とし穴をまとめました。

Gemini API CLI終了コードリトライCI9バッチ処理10シェルスクリプト

壁紙用の画像を夜のうちに 20 枚ぶん作っておき、朝にまとめて選びたいと考えました。そのとき、いちばん困るのは「3 枚目で止まったまま朝を迎える」ことです。

何で止まったのかが分からないと、手が打てません。ネットワークの瞬断なら、もう一度回せば済みます。ところが API キーの失効だったなら、何度回しても同じ場所で落ちます。

10/27 の release notes に、gemini-api というコマンドが載りました。公式ページでは「ステートレスでゼロ依存、AI コーディングエージェント・スクリプト・CI 向け」のベータ版と説明されています。対話型の Gemini CLI とは別物で、API を 1 コマンドずつ呼ぶだけの道具です。

ここでは、このコマンドの終了コードだけに絞ってお伝えします。コマンドの使い方の一覧ではなく、夜間バッチに載せるときの「止める・待つ・飛ばす」の線引きです。

なお、以下は公式ページの記載にもとづく設計メモで、本番で長期間回した記録ではありません。ベータ版ですので、動作は手元で --dry-run から確かめてからお使いください。

公式が決めている終了コードは四つだけ

公式ページの表では、終了コードは次の四つです。

コード意味(公式の記載)私の扱い
0成功次の項目へ
1実行時または API のエラー(ネットワーク、レート制限、失敗したバックグラウンドジョブ)間隔を空けて、上限つきで再試行
2使い方または入力の検証エラー(未知のフラグ、入力の欠落、`--body` のキー重複)再試行しない。その項目だけ記録して飛ばす
3認証または認可の失敗再試行しない。バッチ全体を止める

ここで注目したいのは、1 に三つの性質の違う失敗が入っていることです。瞬断、レート制限、失敗したバックグラウンドジョブは、待てば直るものと、直らないものが混ざっています。

逆に 2 と 3 は、待っても状況が変わりません。入力が壊れているか、鍵が通らないかのどちらかだからです。

原則: 待てば変わる失敗だけを待ち、変わらない失敗は早く止める。

ラッパーを書く

この線引きをそのまま書いたのが、次のスクリプトです。1 項目ずつ run_with_retry に渡し、終了コードで分岐します。

#!/usr/bin/env bash
# nightly-gemini.sh — gemini-api の終了コードで再試行の可否を分ける
set -u
 
MAX_ATTEMPTS=3
LOG_DIR="${LOG_DIR:-./logs}"
mkdir -p "$LOG_DIR" ./out
 
# 戻り値: 0=成功, 2=入力の問題(飛ばす), 3=認証の問題(全体を止める), 1=再試行を使い切った
run_with_retry() {
  local name="$1"; shift
  local attempt=1 code wait_sec
 
  while :; do
    # --agent-mode: エラーを構造化 JSON で stderr に出す。試行ごとに保存しておく
    "$@" --agent-mode 2> "$LOG_DIR/${name}.attempt${attempt}.err"
    code=$?
 
    case "$code" in
      0) return 0 ;;
      2) echo "[$name] 入力の検証エラー(exit 2)。再試行せず飛ばします" >&2; return 2 ;;
      3) echo "[$name] 認証エラー(exit 3)。バッチを止めます" >&2; return 3 ;;
      1)
        if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
          echo "[$name] ${MAX_ATTEMPTS} 回試して失敗(exit 1)" >&2
          return 1
        fi
        wait_sec=$(( 30 * attempt * attempt ))   # 30秒 → 120秒
        echo "[$name] exit 1。${wait_sec} 秒待って再試行 (${attempt}/${MAX_ATTEMPTS})" >&2
        sleep "$wait_sec"
        attempt=$(( attempt + 1 ))
        ;;
      *) echo "[$name] 想定外の終了コード $code" >&2; return "$code" ;;
    esac
  done
}
 
skipped=0; failed=0; line=0
while IFS= read -r prompt; do
  line=$(( line + 1 ))
  n=$(printf '%03d' "$line")   # 入力ファイルの行番号。ログと出力のファイル名が安定する
  run_with_retry "wp-$n" gemini-api image "$prompt" --out "./out/wp-$n.jpg"
  case $? in
    0) ;;
    2) skipped=$(( skipped + 1 )) ;;
    3) echo "認証に失敗したため中断します" >&2; exit 3 ;;
    *) failed=$(( failed + 1 )) ;;
  esac
done < prompts.txt
 
echo "完了: 飛ばした=${skipped} 失敗=${failed}"
[ "$failed" -eq 0 ] && [ "$skipped" -eq 0 ]

ファイル名を入力の行番号から作っているのは、途中で落ちた行があっても、朝に「何行目の画像か」がそのまま分かるようにするためです。

ログを試行ごとに分けているのは、--agent-mode の stderr に残る内容が、あとで 1 の中身(瞬断かレート制限か)を見分ける唯一の手がかりになるからです。エラーの JSON の項目名は公式ページに載っていないため、まず一度わざと失敗させて、実物を見てから集計を書くことをお勧めします。

再実行で「二重に払う」のは、1 の中のバックグラウンドジョブ

ここまでのラッパーは、数秒で終わる画像生成のような処理向けです。

公式の例にある Deep Research のような長いジョブでは、話が変わります。--background を付けると、コマンドは対話の ID をすぐ返すとされています。その ID を持たずに、同じコマンドを丸ごと再実行すると、新しいジョブがもう一つ始まります。

終了コード 1 に「失敗したバックグラウンドジョブ」が含まれる以上、ラッパーが 1 を見て同じコマンドを再実行する設計は、課金を二重に生む可能性があると考えたほうが安全です。

長いジョブは、ラッパーに包まず、次の順で扱うのが私の線引きです。

  1. --background --stream=false で起動して、返ってきた ID をファイルに書き出します
  2. 以降は ID に対して agent status で状態を見ます(出力の形は公式ページで確認してから、パースを書きます)
  3. 止めたいときは agent cancel <id> を使います

つまり、再試行してよいのは「何も始まっていないことが分かる失敗」だけです。ID が手元にあるなら、再実行ではなく問い合わせに切り替えます。

朝ではなく、前の晩に落とす

2 の「入力の検証エラー」は、夜に回してから知るには惜しい失敗です。公式ページによると --dry-run は、入力を検証してリクエストの中身をオフラインでプレビューし、認証情報は伏せて表示します。

そこで、バッチを本番で回す前に、各行を --dry-run だけで通す前段を置きます。

# preflight.sh — 本番の前に、全行の入力だけを検証する
bad=0
while IFS= read -r prompt; do
  if ! gemini-api image "$prompt" --out /dev/null --dry-run >/dev/null 2>&1; then
    echo "検証エラー: $prompt" >&2
    bad=$(( bad + 1 ))
  fi
done < prompts.txt
[ "$bad" -eq 0 ] || { echo "${bad} 行に問題があります" >&2; exit 2; }

--out /dev/null が --dry-run 下で問題なく通るかどうかは、私は公式ページから読み取れませんでした。通らなければ、--out ./out/dryrun.jpg のように普通のパスへ替えてください。

ベータ版を夜間に載せるときの二つの備え

公式ページには、ベータの注意が二つ書かれています。

一つ目は、マイナーバージョンの間でも破壊的変更がありうることです。インストール時に GEMINI_VERSION へリリースタグを指定して、バージョンを固定します。終了コードの意味が変わると、ここで書いた分岐がそのまま崩れるからです。

二つ目は、generate と agent run を -o json や --jq と組み合わせるときは --stream=false が必要なことです。ラッパーの中で -o json を付けたのに何も出ない、という場面ではまずここを疑います。

今夜やること

  • 認証は環境変数 GEMINI_API_KEY で渡します(スクリプトに直書きせず、export GEMINI_API_KEY=YOUR_API_KEY の形で呼び出し側のシェルに置きます)
  • preflight.sh を 3 行ぶんのプロンプトで試し、終了コードが 0 か 2 になることを確かめます
  • わざとキーを一文字変えて run_with_retry を回し、3 で即座に止まることを見ます

終了コードの分岐は、書いた時点では正しそうに見えても、止まる場面を一度自分の目で見るまでは安心できません。実際に落として確かめてから夜に任せるほうが、私は安心できると感じています。個人開発では、朝に原因を調べ直す時間がいちばん惜しいのです。

関連して、同じ Gemini の CLI でも対話型のほうでつまずいたときは Gemini CLI が起動しない・認証が通らないときに見る場所 を、モデルの自動ルーティングで応答が変わったと感じたときは Flash の返事の調子が変わった朝、最初に見るのは自分のプロンプトではありません をあわせてご覧ください。

シェア

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

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

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

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

関連記事

⟐ 開発ツール2026-10-29
gemini-api を入れる前に — Gemini CLI とは別物だと分かってから決めた、置き場所と最初の一手
10/27 に出た Gemini API CLI(gemini-api・ベータ)は、Gemini CLI の後継ではありません。README を読んで分けた置き場所と、課金の前に dry-run で確かめる手順、CI での版固定を整理しました。
⟐ 開発ツール2026-09-02
モデル入れ替えの合否を一致率で決めると、カテゴリ分布のずれは通過します
画像分類バッチのモデルを差し替えたとき、ゴールデンセットの一致率は通るのにカテゴリ構成比だけが動くことがあります。必要サンプル数を実際に計算し、切替前に判定できる対応比較のハーネスを組みました。
⟐ 開発ツール2026-08-28
1日2回のはずの分類バッチが1回しか動かない — 成果物から実行回数を復元する
定期実行のうち片方の枠だけが不発火し、失敗ログが一件も出ないまま処理量が半分になっていた運用の記録です。成果物と未処理残高から実行回数を復元し、起きなかった回を名指しする手順を動くコードでまとめます。