朝、いつものスケジュール実行ログを開くと、ジョブは「成功」と表示されているのに、生成されるはずの成果物がどこにもありませんでした。ログを遡ると、途中で 401 Unauthorized が一度だけ出て、そのあと出力がぷつりと途切れています。
先に結論を書きます。つらいのは 401 そのものではありません。401 のあと、Antigravity CLI が対話的な再ログインプロンプトに落ちてしまい、端末のない無人実行ではその問いに誰も答えられないまま固まる——これが本当の落とし穴です。終了コードだけ見ると成功に見えるのに、中身は空。私自身、個人開発者として Dolice Labs の複数サイトの自動運用を回している中で、この「静かな停止」に一番手を焼きました。
6月18日に Gemini CLI が無料・Pro / Ultra 向けの提供を終え、Antigravity CLI へ一本化されます。既存の gemini 由来の認証で組んでいた自動化は、このタイミングで一度作り直しが必要になります。だからこそ、無人運用の認証は今のうちに設計し直しておきたいところです。
「成功」と表示されたジョブが何もしていない
最初に厄介なのは、症状が成功として記録される点です。スケジューラから見ると、プロセスは戻り値を返して終わっています。けれど実際には、エージェントが本処理に入る前の認証段階で 401 を受け、再ログインを促す対話に入り、入力待ちのまま親プロセスのタイムアウトで畳まれていただけ、ということが起こります。
切り分けの第一歩は、「認証が生きているか」を本処理とは別に確かめることです。本番タスクと認証チェックを混ぜると、どちらで失敗したのか分からなくなります。
#!/usr/bin/env bash
set -euo pipefail
# 本処理に入る前に、認証だけを確かめる。
# 死んでいれば対話プロンプトに落ちる前に、明示的に止める。
if ! antigravity auth status >/dev/null 2>&1; then
echo "[preflight] 認証が無効です。ジョブを中止します。" >&2
exit 78 # EX_CONFIG: 設定不備として明示的に失敗させる
fi
echo "[preflight] 認証 OK"サブコマンド名はバージョンで変わることがあります。手元の版で antigravity --help を一度確認し、認証状態を確かめる軽いコマンドに置き換えてください。要点は「本処理の前に、対話に入らずに認証の生死を判定する」ことです。
なぜ昨日まで動いていたのに、急に止まるのか
原因はほぼ二つに集約されます。
一つは、対話ログインで得たトークンには有効期限があること。手元で一度サインインして動かしたものを、そのままスケジュール実行へ流用すると、数日〜数週間で期限が切れ、ある朝突然 401 になります。動いていた期間があるぶん、原因の見当がつきにくいのが厄介です。
もう一つは、認証の作り替えです。6月18日の移行のように、土台のツールが別実装へ置き換わると、それまで有効だった資格情報が無効化されることがあります。設定ファイルは前と同じに見えても、CLI 側が受け付けなくなっている、という非対称が起こります。
どちらも「設定が間違っている」のではなく「期限が来た・土台が変わった」だけなので、JSON を見直しても直りません。
対話ログインに頼らない認証へ切り替える
無人実行の認証は、人の手を介する OAuth ではなく、環境変数で渡せる鍵に寄せるのが堅実です。鍵方式なら対話プロンプトそのものが発生しないため、401 を踏んでも「再ログイン待ちで固まる」事故が原理的に起きません。
# cron / CI からはサービス用の鍵を環境変数で渡す。
# 鍵は実値をスクリプトに直書きせず、シークレットストアから注入する。
export ANTIGRAVITY_API_KEY="YOUR_ANTIGRAVITY_API_KEY"
antigravity run --non-interactive --input task.md鍵の実値はリポジトリにもログにも残しません。echo でデバッグ出力する誘惑が出ますが、ここは必ず伏せます。環境変数の中身を set -x 付きのスクリプトで流すと履歴に残るので、鍵を扱う区間だけ set +x で囲むと安全です。
set +x # ここから先はトレースを止める
export ANTIGRAVITY_API_KEY="$(cat /run/secrets/antigravity_key)"
antigravity run --non-interactive --input task.md
set -xタイムアウトと「対話落ち」をジョブ側で握る
鍵方式に移しても、保険としてジョブ側でタイムアウトと標準入力を握っておくと、将来別の理由で対話に落ちたときに静かな停止を防げます。標準入力を /dev/null に縛ると、もし対話プロンプトに入っても即座に EOF で抜け、無限待ちになりません。
run_with_guard() {
local timeout_sec="$1"; shift
# 標準入力を /dev/null に縛り、対話に入った瞬間 EOF で終わらせる。
if ! timeout --signal=TERM "${timeout_sec}" "$@" </dev/null; then
local code=$?
echo "[guard] 異常終了 exit=${code}(124 はタイムアウト)" >&2
return "${code}"
fi
}
run_with_guard 600 antigravity run --non-interactive --input task.mdtimeout が 124 を返したら、それは「処理が重い」ではなく「どこかで待ちに入った」合図として扱います。成功扱いで握り潰さず、明示的に失敗として記録するのが、静かな停止を二度と起こさないための分かれ目です。
毎朝、本番の前に認証だけをスモークテストする
最後の予防策は、重い本番ジョブの前に、一分で終わる軽いタスクで認証の生死だけ確かめることです。期限切れを本番のタイミングで踏むのではなく、先回りで気づけます。
# 本番ジョブの前段に置く。応答が返れば認証は生きている。
if antigravity run --non-interactive --input - </dev/null <<'PROMPT' >/dev/null 2>&1
ok とだけ返してください。
PROMPT
then
echo "[smoke] 認証 OK。本番ジョブへ進みます。"
else
echo "[smoke] 認証に失敗しました。鍵の更新が必要です。" >&2
exit 78
fiこのスモークテストを失敗したら、本番には進まず、自分に通知を飛ばすようにしています。鍵を差し替えるのは数分の作業ですが、それに気づくのが「翌朝の空のログを見たとき」だと、丸一日ぶんの成果が失われます。先に小さく確かめておくだけで、その一日が守れます。
次のアクション
いまスケジュール実行に組んでいる Antigravity 関連のジョブを一つ開き、認証が対話ログイン由来か鍵由来かを確かめてみてください。対話ログインのままなら、まず環境変数の鍵方式へ寄せ、preflight の認証チェックを一行足す。この二つだけで、ある朝の静かな停止はかなり防げます。同じように無人運用を続けている方の、朝のログが空にならない助けになれば幸いです。