◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/開発ツール
⟐ 開発ツール/2026-10-17中級

止まるときは静かに止まる — 定期実行の Gemini 呼び出しに、タイムアウト・空出力・多重起動の殻をかぶせる

cron などで `gemini -p` を呼ぶ定期実行が、応答待ちで止まる・終了コード 0 で空を返す・前回分と重なる、の 3 つから身を守る外側のシェルスクリプトです。替え玉コマンドで測った終了コードと所要秒数、タイムアウトが子プロセスをどこまで止めるかも載せました。

gemini-cli8crontimeoutflockautomation37shell

定期実行で gemini -p "..." を呼んでいると、失敗の知らせより先に「何も届かない朝」がやってきます。エラーは出ていません。ログも空のままで、プロセスだけがいつまでも残っています。

応答待ちで止まったコマンドは、自分からは終わりません。終わらないものを外から終わらせる殻を、呼び出しの外側に 1 枚かぶせておくのが、私の今の運用です。

以下では、その殻(約 40 行のシェルスクリプト)と、手元で測った挙動をお伝えします。測定には本物の Gemini を使わず、止まる・空で返る・失敗する振る舞いを再現した替え玉コマンドを使いました。したがって、Gemini 側が実際にどれくらいの頻度で止まるかは、ここでは扱っておりません。外から見えた終了コードと秒数だけの話です。

守る相手は 3 つに絞ります

殻が受け持つのは、次の 3 つだけです。

症状放っておくと殻での扱い
応答待ちで止まるプロセスが残り続け、次の回も動かないtimeout で止める
終了コード 0 なのに出力が空空のファイルが成果物として下流へ流れる0 バイトを失敗として扱う
前回分がまだ動いている同じ処理が重なり、上限や書き込みを奪い合うflock で後発を見送る

3 つとも、呼び出す相手が Gemini でなくても起きます。ですから殻は、相手のコマンドを環境変数で差し替えられる形にしました。

殻の全文です

#!/usr/bin/env bash
# gemini-guard.sh — 使い方: gemini-guard.sh prompt.txt out.md
set -u
 
PROMPT_FILE="${1:?usage: gemini-guard.sh prompt.txt out.md}"
OUT="${2:?usage: gemini-guard.sh prompt.txt out.md}"
LIMIT="${GUARD_LIMIT:-120}"          # 秒。ここを超えたら止める
GRACE="${GUARD_GRACE:-10}"           # 停止要求から強制終了までの猶予(秒)
LOCK="${GUARD_LOCK:-/tmp/gemini-guard.lock}"
LOG="${GUARD_LOG:-$HOME/gemini-guard.jsonl}"
CMD="${GUARD_CMD:-gemini}"
 
log() {  # log <status> <rc> <seconds> <bytes>
  printf '{"ts":"%s","status":"%s","rc":%s,"sec":%s,"bytes":%s}\n' \
    "$(date -u +%FT%TZ)" "$1" "$2" "$3" "$4" >> "$LOG"
}
 
# 前回がまだ動いていたら、重ねずに諦める
exec 9>"$LOCK"
if ! flock -n 9; then
  log skipped_locked 0 0 0
  exit 75
fi
 
start=$(date +%s)
timeout --kill-after="$GRACE" "$LIMIT" "$CMD" -p "$(cat "$PROMPT_FILE")" \
  > "$OUT.tmp" 2> "$OUT.err"
rc=$?
sec=$(( $(date +%s) - start ))
bytes=$(wc -c < "$OUT.tmp" | tr -d ' ')
 
case "$rc" in
  0)
    if [ "$bytes" -eq 0 ]; then
      log empty_output "$rc" "$sec" "$bytes"; rm -f "$OUT.tmp"; exit 3
    fi
    mv "$OUT.tmp" "$OUT"            # 完成してから置く
    log ok "$rc" "$sec" "$bytes"; exit 0 ;;
  124) log timeout "$rc" "$sec" "$bytes"; rm -f "$OUT.tmp"; exit 124 ;;
  137) log killed  "$rc" "$sec" "$bytes"; rm -f "$OUT.tmp"; exit 137 ;;
  *)   log failed  "$rc" "$sec" "$bytes"; rm -f "$OUT.tmp"; exit "$rc" ;;
esac

各行の理由を、順に書き残します。

.tmp に書いてから mv で本来の名前に置くのは、途中で止まったときに「半分だけ書かれた成果物」を下流に見せないためです。mv は同じディレクトリ内なら一瞬で終わります。

終了コードは、止まった理由ごとに変えました。124 は timeout が時間切れで止めたとき、137 は停止要求に応じず強制終了したとき、75 は後発として見送ったときです。cron のメールや監視側で、原因が一目で分かります。

替え玉で測った結果です

制限 3 秒・猶予 2 秒に縮めて、5 通りの替え玉を動かしました。

替え玉の振る舞い終了コード所要成果物ログの status
すぐ答えて終わる00 秒置かれるok
応答せず待ち続ける1243 秒置かれないtimeout
停止要求(TERM)を無視する1375 秒置かれないkilled
何も出さず 0 で終わる30 秒置かれないempty_output
エラー終了(1)する10 秒置かれないfailed

停止要求を無視する場合の 5 秒は、制限の 3 秒に猶予の 2 秒を足した値です。--kill-after を付けていなければ、この替え玉は 60 秒の待機が終わるまで残ります。TERM を握りつぶすプログラムは珍しくないので、猶予は外せません。

重なりの確認もしました。止まる替え玉を先に起動し、1 秒後にもう 1 本を起動すると、後発は即座に終了コード 75 で引き下がり、先発は 3 秒で 124 になりました。ログには skipped_locked と timeout が並びます。

子プロセスまで止まるかは、--foreground で変わります

測っていて意外だったのが、孫プロセスの扱いです。bash スクリプトが中で sleep 77 を呼ぶ替え玉を、2 通りで止めて数えました(GNU coreutils 8.32)。

呼び方終了コード止めたあとに残った sleep
timeout 2 ./stub1240 本
timeout --foreground 2 ./stub1241 本

既定の timeout は、コマンドを別のプロセスグループで走らせ、時間切れでグループごと止めます。--foreground を付けるとグループを分けないので、止まるのは直接の子だけになり、その先の孫が居残ります。同じ理由で、コマンドの内側で setsid を使うと居残りました。

殻の中では --foreground を使っておりません。gemini が内部で別プロセスを起こす場合に、片付けが外から効くようにするためです。端末から手で叩くときは --foreground が必要になる場面もあるので、用途で選んでください。

cron への載せ方

# 毎朝 6:10。後発が重なっても殻が見送る
10 6 * * * GUARD_LIMIT=180 /home/me/bin/gemini-guard.sh /home/me/jobs/morning.txt /home/me/out/morning.md

あわせて、ログの末尾を見る 1 行を置いておくと、朝の確認が楽になります。

tail -n 3 ~/gemini-guard.jsonl

ok 以外が並んでいたら、out/morning.md.err に標準エラーが残っています。成功した回の .err は、私は気づいたときにまとめて消しています。

macOS には標準の timeout がありません。Homebrew の coreutils を入れ、gtimeout に読み替えれば同じ動きになります。flock も標準では入っていないため、別の排他(mkdir で作る簡易ロックなど)に置き換えてください。

殻の限界です

殻は、止まったことを教えてくれますが、止まった原因は教えてくれません。応答待ちが Gemini 側なのか、ネットワークなのか、プロンプトが重すぎるのかは、.err と所要秒数の推移から推し量るしかありません。

また、制限時間の決め方には正解がありません。私は、普段の所要秒数の 3〜4 倍あたりから始め、ok のログが溜まってから詰めるようにしています。最初から短く締めると、正常な長い回を殺してしまうからです。

終わらないものは、中から直すより先に、外から終わらせるのが私の線引きです。 そのうえで、終わらなかった事実をログに残しておきます。次に手を入れるのは、ログが原因を指してからで遅くないのだと思います。

振り返ると、止まった朝にいちばん困ったのは、原因が分からないことよりも、止まった事実すら残っていないことでした。

関連して、CLI に依存した定期実行そのものを組み替える話は「Gemini CLI 停止に備え、夜間バッチに google-genai SDK フォールバックを組み込む」に、結果を取りこぼさない設計は「投げて終了する定期実行で、結果を取りこぼさない — Gemini バックグラウンド実行を再取得台帳で回す設計」にまとめています。

シェア

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

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

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

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

関連記事

⟐ 開発ツール2026-09-15
承認ルールを書いたのに一度も一致していませんでした — policy を点検して回った記録
Gemini CLI の policy に書いた承認ルールが一度も一致していませんでした。照合対象が JSON 文字列だったこと、置き場所の階層が無効だったこと、無人実行で ask_user が deny になること。点検を機械化したスクリプトまで書き残します。
⟐ 開発ツール2026-10-14
クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep して、GEMINI.md には索引だけ置く
アプリのクラッシュ報告を調べるとき、過去の不具合の全文を毎回読ませずに済ませる運用です。ID から 1 行を引く早見表と、GEMINI.md に置く範囲の決め方、そのまま使える grep 関数をまとめました。
⟐ 開発ツール2026-09-29
Gemini CLI に新しいキーを渡しても「API key not valid」。読まれていたのは三か所目の古いキーでした
Google ログインから有料 API キーへ Gemini CLI の認証を切り替えたのに、400 の「API key not valid」が消えませんでした。キーの出どころはシェル・作業ディレクトリの .env・ホームの .env・settings.json の四か所あり、順に照らす小さなスクリプトで原因を一つに絞ります。