◉ANTIGRAVITY LABEN
記事一覧/アプリ開発
▣ アプリ開発/2026-10-08中級

agy CLI をコンテナで動かす前に、擬似 agy で確かめておく 3 つの安全網

Antigravity CLI(agy)を Docker や Cloud Run のジョブから呼ぶ前に、インストーラーの取りこぼし・承認待ちの固まり・終了コード 0 の空振りを、擬似 agy で再現して潰します。公式 docs の記述と、手元で実行した結果だけを根拠に組み立てた手順です。

antigravity459agy4CLI7Docker5Cloud Runheadless4automation54

agy -p が手元でうまく動いたので、そのままコンテナに載せて夜間に回してみたいと考えたとき、私が最初に不安になったのは agy そのものではなく、「固まったときに、呼び出し側がそれに気づけるか」でした。

そこで、実物の agy は使わずに、わざと固まる擬似 agy のシェルスクリプトを作って、呼び出し側の安全網を先に組んでみました。以下に出てくるコードと出力は、すべて手元で実行したものです。実物の agy を Cloud Run で動かした記録ではありません。その部分は、すでに実体験を公開してくださっている方の記事を参照し、公式 docs で裏取りできた範囲だけを書きます。

最初にお伝えしたいのは、順番です。コンテナに載せる前に認証の出どころを決めておくこと。次に、固まったときの出口を呼び出し側に持たせること。この 2 つが先で、プロンプトの工夫はその後です。

先に押さえる事実(公式 docs と公開されている報告)

実際に Docker と Cloud Run のジョブから agy を呼んだ方の記録が、Zenn に公開されています(Antigravity CLI(agy)を Docker / Cloud Run から呼び出すときにハマること)。その内容と、公式のインストールページ・GitHub の Issue を突き合わせると、次の点が確認できます。

論点確認できたこと出どころ
ヘッドレスの認証公式 docs が挙げるのは Gemini API キー方式。settings.json に modelProvider を gemini と書き、環境変数 GEMINI_API_KEY を渡す。環境変数だけでは効かない、と明記されている公式インストールページ
サービスアカウント/ADC非対話認証を求める Issue(#223)で、GOOGLE_APPLICATION_CREDENTIALS などを試しても OAuth の案内が出たと報告されている。公式の回答は、確認した時点のページ上には見当たらないGitHub Issue #223
手元で動いた理由以前のブラウザログインのトークンが残っていた、という指摘。OS のキーチェーンに有効なトークンがあればブラウザなしでサインインする、と公式 docs にもある公式インストールページ/Zenn の記録
承認待ちの固まりツール実行の承認を誰も返せず --print-timeout まで待ち、終了コード 0 で部分出力を返した、という報告Zenn の記録

ひとつ注意があります。--print-timeout と --dangerously-skip-permissions は、私が確認した公式インストールページには記載がありませんでした。Zenn の記録では使われているので、お使いの版の agy --help で存在を確かめてから使ってください。版は固定されておらず、この記録では手元が 1.0.15、コンテナが 1.2.16 だったそうです。

安全網 1:インストーラーを「そのまま bash に流さない」

Zenn の記録によると、curl ... | bash で取得したインストーラーが、同じ URL なのに、ときどき gzip のまま(Content-Encoding なし)で返ってきたそうです。私の環境で同じ現象は再現していません。ただ、「gzip のまま bash に渡すとどうなるか」と「いったんファイルに落として判定する書き方が正しく動くか」は、手元で確かめられます。

# 普通のスクリプトと、gzip したスクリプトを用意
printf '#!/bin/bash\necho INSTALL_OK\n' > plain.sh
gzip -c plain.sh > gz.raw
cp plain.sh plain.raw
 
for f in plain.raw gz.raw; do
  if gzip -t "$f" 2>/dev/null; then
    gunzip -c "$f" > out.sh; echo "$f: gzip -> decompressed"
  else
    cp "$f" out.sh; echo "$f: plain"
  fi
  bash out.sh
done

実行結果は次のとおりです。

plain.raw: plain
INSTALL_OK
gz.raw: gzip -> decompressed
INSTALL_OK

gzip のまま cat gz.raw | bash に流すと、bash は $'\037\213...' のようなバイナリ文字列をコマンドとして解釈しようとして、意味のあるエラーを残さないまま終わりました。ビルドログに残るのが「bash: line 1: ...」という崩れた 1 行だけだと、原因にたどり着くまでに時間がかかります。判定を 3 行挟むだけで、この種の迷子は避けられます。

実際の Dockerfile では、非 root ユーザーを作ってから、この判定つきのインストールを行い、最後に agy --version でビルド時に存在確認をします。インストーラーは ~/.bashrc を書き換えますが、CMD はそれを読まないので、PATH は ENV で渡す必要がある、という点も Zenn の記録にありました。

安全網 2:呼び出し側に「期限」と「閉じた stdin」を持たせる

承認待ちで固まる挙動を、擬似 agy で再現します。--dangerously-skip-permissions が付いていなければ待ち続け、待った後は「部分出力を返した」というメッセージを出して終了コード 0 で終わる、という振る舞いをシェルで書きます。

#!/bin/bash
# fake_agy.sh — 承認待ちで固まる擬似 agy
for a in "$@"; do [ "$a" = "--dangerously-skip-permissions" ] && SKIP=1; done
if [ -z "$SKIP" ]; then
  echo "waiting for tool approval..."
  sleep ${FAKE_HANG:-30}
  echo "print timeout after 2m0s with turn in progress; returning partial output"
  exit 0
fi
echo OK > "${FAKE_OUT:-result.txt}"
echo "done"; exit 0

これを呼ぶ側の Python が、次の run_agent です。ポイントは 3 つあります。stdin を DEVNULL にして入力待ちで止まらないようにすること。呼び出し側に独自の期限を持つこと。そして、成否を終了コードではなく「成果物のファイルがあるか」で決めることです。

import os, subprocess, time, pathlib
 
def run_agent(cmd, workdir, expect_file, timeout_s, env=None):
    """exit code を信用しない。期限・stdin 閉鎖・成果物の存在で成否を決める。"""
    env = {**os.environ, **(env or {})}
    for k in ("APP_PASSWORD", "SESSION_SECRET"):   # エージェントに不要な秘密は渡さない
        env.pop(k, None)
    proc = subprocess.Popen(cmd, cwd=workdir, stdin=subprocess.DEVNULL,
                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                            text=True, errors="replace", env=env)
    deadline = time.time() + timeout_s
    killed = False
    while proc.poll() is None:
        time.sleep(0.2)
        if time.time() > deadline:
            proc.kill(); proc.wait(); killed = True; break
    out = proc.stdout.read()
    produced = pathlib.Path(workdir, expect_file).is_file()
    return {"exit": proc.returncode, "killed": killed,
            "produced": produced, "ok": produced and not killed,
            "tail": out[-200:]}

4 つの場面で動かした結果を並べます。期限は短くして、FAKE_HANG で固まる秒数を調整しました。

場面exitkilledproducedok
フラグなし・期限 3 秒・10 秒固まる-9(kill 後に回収)TrueFalseFalse
フラグなし・期限 10 秒・2 秒で「部分出力」を返して終了0FalseFalseFalse
フラグあり・成果物を書いて終了0FalseTrueTrue
フラグあり・成果物を別の名前で書いて終了0FalseFalseFalse

表の 2 行目と 4 行目が、この記事でいちばん伝えたい部分です。終了コードは 0 なのに、ok は False になります。「終了コード 0 なら成功」という判定だと、この 2 つは成功として数えられ、後段に空のディレクトリが流れていきます。

最初のコードでは kill() のあとに wait() を入れていませんでした。その結果、1 行目の exit が None のままログに残りました。回収を足して -9 が見えるようにしたのは、擬似 agy で試したからこそ気づけた修正です。

安全網 3:「動いた」ではなく「使うツールが動いた」を確かめる

Zenn の記録で印象に残ったのは、エージェントが「完了しました」と報告していても、ログには自作の評価スクリプトが毎回例外を出している、という場面でした。エージェントはそれを環境の問題と見なして評価を飛ばし、失敗を伝えないまま終えていたそうです。

ここは擬似 agy では再現できない部分なので、手元で確かめたことではなく、記録から学んだ線引きとして書きます。

  • agy が OK と返すかどうかに加えて、エージェントに呼ばせるツールを 1 回ずつ単体で動かす自己診断モードを、ジョブに持たせます
  • 自己診断は、本番と同じ引数で走らせます(記録では、フラグを省いた自己診断が、承認待ちの固まりをそのまま再現したそうです)
  • 成功の判定は、エージェントの報告ではなく、呼び出し側が成果物を検査して行います

安全網 2 の expect_file は、この「呼び出し側が検査する」の最小形です。ファイルが存在するだけでは足りない場面では、中身の検査をここに足していくことになります。

認証についての、最初の決め事

最後に、冒頭の「認証の出どころを先に決める」に戻ります。公式 docs が示すヘッドレスの方式は、Gemini API キーでした。その場合、環境変数だけでは効かず、settings.json で modelProvider を gemini にする必要があります。ここは私自身は実物で確かめていません。公式の記述として引用するにとどめます。

また、サービスアカウントや ADC で動かしたい場合は、Issue #223 のとおり、確認した時点では公式の対応が見当たりませんでした。「手元で動いたからコンテナでも動く」は、キーチェーンに残ったログイン情報のおかげかもしれません。コンテナに載せる前に、ブラウザログインを一度すべて捨てた状態で agy -p を動かしてみる——その確認が、いちばん安く済みます。

手元で動いたことと、無人で動き続けることのあいだを取り持つのは、呼び出し側の検査です。

次の一歩として、お使いの環境で agy --help を開き、--print-timeout と --dangerously-skip-permissions が実在する版かどうかを確かめるところから始めていただければと思います。許可の扱いそのものについては、承認ダイアログが繰り返し出るときの切り分けも参考になります。

シェア

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

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

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

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

関連記事

▣ アプリ開発2026-08-24
消えたのは中身ではなく作業ディレクトリでした — 無人で走る破壊操作を fail-closed に組み直す
無人で走らせていた掃除処理が、掃除対象の中身ではなく作業ディレクトリそのものを消しました。mkdir -p が安全弁にならなかった理由と、取得失敗を既定値で埋める書き方が破壊側の分岐を選んでいた経緯を、実際の検証出力とともに整理します。
▣ アプリ開発2026-04-19
Antigravity で構築する iOS アプリ全自動リリースパイプライン — AI がスクリーンショット生成・メタデータ最適化・審査対応まで担う上級実装ガイド
Antigravity × App Store Connect API × GitHub Actions で、ビルドからApp Store審査提出まで全工程を自動化。AI によるスクリーンショット説明文生成・メタデータ最適化・リジェクト対応提案を完全実装する上級ガイド。
▣ アプリ開発2026-09-10
iOS 27 の配信日が決まってから、私が最初に grep したのは AGENTS.md でした
iOS 27 と iPadOS 27 の配信は9月14日です。版数の文字列はソースだけでなく、エージェントが読む設定ファイルにも残ります。両方を数えるスクリプトと、keyword 検索では拾えない一行の話を書き残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます