Antigravity の Agent に「本番サーバーにデプロイしておいて」とだけ指示した直後、ターミナルパネルの出力が password: の表示で止まったまま、3分経っても4分経っても何も進まない。あの数分間の沈黙を、私は半年で20回以上見てきました。Dolice Labs の 4 サイトを Antigravity で並行運用していると、デプロイ・依存解決・Git の対話的リベース・SSH トンネル越しのコマンドなど、思っているよりはるかに多くの場面で「入力待ちで Agent が固まる」事象に出くわします。
このとき Agent は決して暴走しているわけでも、無限ループに陥っているわけでもありません。OS から見れば「read(0, ...) で標準入力をブロック中」という、非常に行儀の良い状態で待機しています。問題は、Agent のターミナルが疑似端末(PTY)を割り当てない設計になっているため、入力プロンプトを返す相手がそもそも存在しない点です。
個人開発者として 2014 年から AdMob で収益化した壁紙アプリ群を運用し、累計5,000万ダウンロード規模のサービスを 1 人で支えてきた経験から、この種の「沈黙する Agent」は CI のジョブ詰まりと並んで業務効率を最も削るパターンだと感じています。今回はこの根本原因と、私自身が AGENTS.md に書き込んでいる恒久的な防御策を共有します。
なぜ Agent ターミナルでは sudo / ssh が固まるのか
通常のターミナルは、シェルを起動するときに PTY(pseudo-terminal)を割り当てます。tty コマンドを叩いて /dev/ttys000 のようなパスが返ってくれば、それが PTY です。sudo も ssh も git rebase -i も、内部で isatty(0) を呼んで「標準入力が端末か」を確認します。端末だと判定された場合のみ、パスワードプロンプトやエディタ起動などの対話機能が動きます。
Antigravity の Agent が npm run dev のようなコマンドを実行するときに使うターミナルは、見た目はターミナルですが、内部的には Agent との JSON-RPC で結ばれたストリームです。PTY が割り当てられていない、または割り当てられていても Agent が stdin をフックしていないため、isatty(0) が false を返します。
すると、コマンドによって挙動が3パターンに分岐します。
sudo: 標準入力が端末でないと判定すると、sudo: a terminal is required to read the passwordというエラーで即時終了する場合と、設定によっては無音でブロックする場合があるssh: パスワード認証が必要な接続ではPermission denied (publickey,password)まで進んで失敗するか、Pseudo-terminal will not be allocated because stdin is not a terminal.という警告と共にリモートシェルが反応しなくなるgit rebase -i:core.editorで指定されたエディタ(多くはvi/nano)を起動するが、PTY がないため画面描画ができず、Agent からは「出力が止まった」ようにしか見えない
つまり、Agent が固まったように見えるのは、コマンド側が「入力できる相手がいないから永遠に待っている」状態を素直に表現しているだけです。
診断: いまの沈黙は本当に TTY 不在が原因か
経験則として、Antigravity で Agent ターミナルが30秒以上沈黙したら、まず TTY 不在を疑います。具体的には、Agent に次のスニペットを実行してもらうと一発で切り分けられます。
# 現在のシェルに PTY が割り当てられているかを確認
tty
# 期待される出力(PTY あり): /dev/ttys003 などのパス
# 問題のある出力: "not a tty"
# 直近で固まったコマンドのプロセスを特定
ps -ef | grep -E "(sudo|ssh|vi|nano|git rebase)" | grep -v grep
# STAT 欄が "S+" や "Ss+" になっていれば前景プロセスとして入力待ち
# "T" なら停止中(SIGTSTP)、"Z" ならゾンビtty が not a tty と返してきたら、これから走らせる対話型コマンドは原則すべて同じ運命をたどります。ここで「Agent が悪い」と決めつけず、コマンド側の対話要求自体を消す方向に倒すのが正解です。
私の運用ログを見返すと、診断ステップを省略して再起動だけで凌いだ日と、しっかり原因を切り分けた日では、復旧後の再発率がまったく違いました。後者は同じ罠を踏まないように AGENTS.md に書き残せるからです。
対策1: パスワードプロンプトをそもそも出さない
sudo で固まるケースは、9 割以上が NOPASSWD 未設定の sudoers が原因です。Agent に毎回パスワードを入力させる構成は本質的に破綻しています。CI 用のサービスアカウントと同じ設計を、ローカルの開発ホストにも持ち込むのが安全です。
# /etc/sudoers.d/antigravity-agent を作成(root で実行)
# Agent が必要なコマンドだけを限定して許可するのがコツ
cat << 'EOF' | sudo tee /etc/sudoers.d/antigravity-agent
# Antigravity Agent から呼ばれる前提のコマンドのみ NOPASSWD 化
%admin ALL=(ALL) NOPASSWD: /usr/bin/lsof, /usr/bin/kill, /usr/sbin/nginx -s reload
EOF
sudo chmod 0440 /etc/sudoers.d/antigravity-agent
# 期待される出力: なし(visudo -c で構文チェック成功)
sudo visudo -c -f /etc/sudoers.d/antigravity-agentポイントは「全コマンドを NOPASSWD: ALL にしない」ことです。Agent が誤って rm -rf / を叩いた瞬間に止められるよう、許可コマンドはホワイトリスト方式で絞り込みます。私はサイトごとに必要な操作(ポート解放・nginx リロード・ログローテーション)だけを並べた sudoers ファイルを _documents/sudoers/ に保管しています。
対策2: SSH は鍵認証 + BatchMode で対話を消す
ssh の沈黙対策は、パスワード認証の余地を完全に断つことです。Agent に「対話できないのに何も聞くな」と SSH 側へ宣言する BatchMode=yes を使います。
# ~/.ssh/config に対象ホスト用エントリを追加
cat << 'EOF' >> ~/.ssh/config
Host antigravity-deploy
HostName deploy.example.internal
User deploy
IdentityFile ~/.ssh/keys/deploy_ed25519
IdentitiesOnly yes
BatchMode yes
StrictHostKeyChecking accept-new
ServerAliveInterval 30
ServerAliveCountMax 3
EOF
chmod 600 ~/.ssh/configBatchMode yes を入れておけば、鍵が拒否された瞬間に SSH は確定的に終了します。これだけで「password: で固まる」事象は構造的に消えます。StrictHostKeyChecking accept-new は初回接続時のフィンガープリント確認も自動化するための設定で、知らないホストへの接続は普通に拒否してくれるので、安全性を大きく下げる類のオプションではありません。
ServerAliveInterval 30 を入れているのは、ネットワークの瞬断で SSH トンネルがゾンビ化するのを避けるためです。Antigravity の Agent はトンネル経由で長時間プロセスを監視する場合があり、無設定だとファイアウォール側の NAT セッションが切れた後に永遠に応答を待ち続けます。
対策3: Git の対話的操作は非対話モードに置き換える
git rebase -i を Agent に任せると、ほぼ確実に固まります。代替手段としては、コミット並び替えを git rebase --onto の機械的な指定に置き換えるか、GIT_SEQUENCE_EDITOR に非対話スクリプトを渡す方法があります。
# 直近5コミットをすべて squash して1つにまとめる例
# GIT_SEQUENCE_EDITOR にスクリプトを渡すと、todo リストを自動で書き換えてくれる
GIT_SEQUENCE_EDITOR='sed -i -e "2,\$s/^pick/squash/"' \
GIT_EDITOR=true \
git rebase -i HEAD~5
# 期待される出力: Successfully rebased and updated refs/heads/<branch>.GIT_EDITOR=true を渡しているのは、squash 後のコミットメッセージ編集画面で true コマンド(何もせず成功で終わる)が起動し、結果的に既存メッセージをそのまま採用してくれるためです。Agent に手作業を期待しないこの組み合わせは、master ブランチへのマージ前にコミットを整える定型作業として、私の 4 サイトでも頻繁に登場します。
git commit で core.editor が起動して固まるケースには、-m "..." を必ず付ける運用に倒すのが最も簡単です。AGENTS.md に「コミット時は必ず -m を使うこと」と書いておくと、Agent はそのルールを尊重します。
対策4: AGENTS.md に「対話禁止リスト」を明文化する
ここまでの対策を Agent に毎回口頭で伝えるのは現実的ではありません。プロジェクトルートの AGENTS.md に「対話プロンプトが出るコマンドは禁止」と明文化しておくと、再発率が体感で8割減りました。
# ターミナル操作のルール(Agent 共有)
## 禁止: 対話プロンプトが発生するコマンド
- `sudo` をパスワードプロンプト付きで使う(NOPASSWD 設定済みコマンドのみ許可)
- `ssh user@host`(必ず ~/.ssh/config のホストエイリアスを使う)
- `git rebase -i`(GIT_SEQUENCE_EDITOR を必ず指定)
- `git commit`(`-m` 必須、エディタ起動禁止)
- `npm init` / `pnpm init`(`-y` 必須)
- `apt install` / `apt remove`(`-y` 必須)
## 必須: 対話を消す環境変数
プロジェクトの `.envrc` で以下を export する:
export DEBIAN_FRONTEND=noninteractive export CI=true # 多くの CLI が対話モードを自動で切る export GIT_TERMINAL_PROMPT=0 export NPM_CONFIG_YES=true
## 30 秒以上の沈黙を検知したら
Agent はコマンドを SIGTERM で中断し、`tty` と `ps -ef` の結果を含めて報告する。
人間の確認なしに `kill -9` で潰さないこと。
CI=true は多くの CLI ツール(Vercel CLI・Cloudflare Wrangler・Stripe CLI など)が対話モードを自動で抑制する合言葉になっています。Antigravity の Agent ターミナルは厳密には CI 環境ではありませんが、対話機能を一律で切るには十分に強力なフラグです。
対策5: それでも対話が必要な場面は expect で機械化する
レガシーなツールや、どうしても初回起動でウィザード形式の入力を要求するソフトに当たることがあります。その場合は expect で対話を完全にスクリプト化し、Agent には expect スクリプトを呼ばせます。
# 例: 初回セットアップで Y/n を3回聞いてくる古いツール
cat << 'EOF' > /tmp/setup.expect
#!/usr/bin/env expect -f
set timeout 60
spawn legacy-tool --init
expect "Continue? \[Y/n\]"
send "y\r"
expect "Overwrite config? \[Y/n\]"
send "y\r"
expect "Enable telemetry? \[Y/n\]"
send "n\r"
expect eof
EOF
chmod +x /tmp/setup.expect
/tmp/setup.expect
# 期待される出力: legacy-tool の通常完了メッセージexpect は Linux/macOS の標準パッケージで配布されており、Antigravity の Agent ターミナルから問題なく呼び出せます。プロンプト文字列が変わるリスクはありますが、対話を完全に潰せる最後の手段として知っておくと、CI 移行できないレガシーツールが残るプロジェクトで重宝します。
沈黙したときの即応フロー(保存版)
最後に、私が Antigravity を使っていて「あ、また固まった」と気づいたときに毎回叩いている復旧フローを並べます。Agent に丸投げできるよう、コマンドだけ並べた短いプレイブックです。
# 1. いま動いているプロセスを確認(前景の対話待ちを特定)
ps -ef | grep -E "(sudo|ssh|vi|nano|nano|git)" | grep -v grep
# 2. 該当 PID を優しく止める(graceful shutdown を尊重)
PID=<上で見つけた PID>
kill -15 $PID
# 3. 5秒待っても残るなら SIGINT、それでも残るなら SIGKILL
sleep 5 && kill -2 $PID 2>/dev/null
sleep 5 && kill -9 $PID 2>/dev/null
# 4. PTY 状態を再確認して、次に走らせるコマンドの選定をやり直す
ttyこの 4 行を Agent に投げるだけで、ほとんどの「沈黙する Agent」事象は60秒以内に復旧します。同時に AGENTS.md へ「今回固まった原因と恒久対策」を1行追記しておくと、同じ罠を半年後の自分が踏まずに済みます。Dolice Labs の 4 サイトを 1 人で回している身としては、こうした小さな積み重ねがメンテナンス工数の差を大きく決めると感じています。
同じように「Agent が黙ったら原因を残す」習慣が誰かの作業時間を取り戻すきっかけになれば嬉しいです。お読みいただきありがとうございました。