ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-08-17中級

サブエージェントが完了せず止まるとき、親側の未承認アーティファクトを先に確認する

サブエージェントに任せた工程が朝まで終わっていない。原因が子ではなく親の承認待ちだったときの切り分け方を、ログから滞留を洗い出す短いスクリプトと、8月に入った「always proceeds」の使いどころ、自動承認をどこで止めるかの判断とあわせてまとめました。

サブエージェント3承認ログ2個人開発95エージェント運用15

夜のうちにリリース前の確認をエージェントへ渡して、朝に結果を見る。個人開発でアプリを何本も抱えていると、この回し方に頼る日が増えます。工程を分けてサブエージェントへ委ねれば、寝ている間に一巡してくれるはずでした。

その朝、画面はまだ動いている顔をしていました。スピナーは回っていて、エラーも出ていません。ただ、最後の出力の時刻が 30 分以上前で止まっていました。

サブエージェント側のログを開いて、原因を探しました。異常な行は見当たりません。モデルもツールも、待っているだけです。

見るべき場所を間違えていました。止まっていたのは子ではなく、親のほうでした。

承認を待っているのが親なら、子はどれだけ健全でも進めません

サブエージェントが親から委譲を受けて走るとき、生成したアーティファクトの扱いは親の側に返ります。その親が別の確認ダイアログを出したまま止まっていると、子から戻ってきた承認要求は処理される順番が回ってきません。

子のログには「待っている」以外の情報が出ません。エラーではないので、タイムアウトも発火しないことがあります。結果として、いちばん静かな状態で朝まで残ります。

この構図は Google も課題として認識していて、2026 年 8 月には親がブロックされた状態でサブエージェントが待ち続ける問題への対処として、アーティファクトを自動承認する「always proceeds」モードが入りました。裏を返せば、それまでは待ち続けるのが既定の挙動だったということです。

同じ「終わらない」でも、フックの待ち方に起因するものは切り分けの順序が違います。そちらはフックの待ち方が 2.6.0 で変わりました。終わらないターンを切り分ける手順に分けて書いています。今回扱うのは、フックを使っていなくても起きるほうです。

滞留している会話をログから洗い出す

目視で追うのは、階層が二段を超えたところで無理になります。私はログを機械的に走らせて、承認要求を出したまま次のイベントが来ていない会話を出すようにしました。

8 月の更新で、委譲されたサブエージェントには subagent_info が付き、その中に conversation_idlog_uri が入るようになりました。親子の突き合わせが、勘ではなく識別子でできます。ツール呼び出しごとの tool_info には正規化されたツール名が入るので、何を待っているのかもそこから読めます。

先に、自分のログに実際どのイベント名が出ているかを確かめてください。名前はバージョンで変わります。

# types.py — 手元のログにどの type が出ているかを数える
import json, sys, collections
 
c = collections.Counter()
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        ev = json.loads(line)
    except json.JSONDecodeError:
        continue
    c[ev.get("type", "(type なし)")] += 1
 
for k, v in c.most_common():
    print(f"{v:>5}  {k}")

手元の環境(Python 3.10.12)で流すと、こう出ました。

    2  approval_request
    2  subagent_start
    2  tool_call
    1  turn_start
    1  result
    1  heartbeat

ここで得た名前を、次のスクリプトの判定に入れます。承認要求を記録し、解決または結果が来たら消す。最後まで残ったものが滞留です。

#!/usr/bin/env python3
"""stream-json の行を読み、承認待ちのまま止まっている会話を洗い出す。"""
import json, sys
from datetime import datetime, timezone
 
PENDING = {}   # conversation_id -> (時刻, 待っているツール名)
LAST    = {}   # conversation_id -> 最終イベント時刻
PARENT  = {}   # 子 conversation_id -> 親 conversation_id
LOGURI  = {}   # conversation_id -> log_uri
 
def ts(v):
    return datetime.fromisoformat(v.replace("Z", "+00:00"))
 
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        ev = json.loads(line)
    except json.JSONDecodeError:
        continue    # 途中で切れた行は捨てる。走行中のログでは普通に起きます
 
    sub = ev.get("subagent_info") or {}
    cid = sub.get("conversation_id") or ev.get("conversation_id")
    if not cid:
        continue
    if sub.get("log_uri"):
        LOGURI[cid] = sub["log_uri"]
    if sub.get("parent_conversation_id"):
        PARENT[cid] = sub["parent_conversation_id"]
 
    t = ts(ev["timestamp"])
    LAST[cid] = t
 
    kind = ev.get("type")
    if kind == "approval_request":
        PENDING[cid] = (t, (ev.get("tool_info") or {}).get("name", "unknown"))
    elif kind in ("approval_resolved", "result"):
        PENDING.pop(cid, None)
 
now = max(LAST.values()) if LAST else datetime.now(timezone.utc)
rows = sorted(
    ((round((now - t).total_seconds()), cid, tool,
      PARENT.get(cid, "-"), LOGURI.get(cid, "-"))
     for cid, (t, tool) in PENDING.items()),
    reverse=True,
)
 
if not rows:
    print("承認待ちで滞留している会話はありません")
    sys.exit(0)
 
print(f"{'wait_s':>7}  {'conversation_id':<16} {'tool':<12} {'parent':<16} log_uri")
for sec, cid, tool, par, uri in rows:
    print(f"{sec:>7}  {cid:<16} {tool:<12} {par:<16} {uri}")
 
blocked = {r[3] for r in rows if r[3] != "-"}
for cid, _ in PENDING.items():
    if cid in blocked:
        print(f"\n{cid} は自身も承認待ちです。子はここが動くまで進めません。")

冒頭の朝と同じ形のログを作って流すと、次の出力になりました。

 wait_s  conversation_id  tool         parent           log_uri
   2028  conv-parent-01   write_file   -                -
   1977  conv-child-a     write_file   conv-parent-01   file:///logs/conv-child-a.jsonl

→ conv-parent-01 は自身も承認待ちです。子はここが動くまで進めません。

読みたいのは順位ではなく、最後の 1 行です。滞留の先頭は親で 2,028 秒、子は 1,977 秒。51 秒しか違いません。子は自分の要求を出してから、ほぼずっと親に足を止められていたことになります。

親の承認を解決した行を足して流し直すと、出力は「承認待ちで滞留している会話はありません」に変わりました。子だけを再実行しても、この状態は解けません。

書き込み中のログに対しても、そのまま流せます。コピーを指すか、末尾を流し込んでください。途中で切れた最終行は捨てられるので、走行中でも止まりません。私は朝いちばんにこれを走らせてから、開くログを決めるようにしました。親が滞留していればダイアログを片づけに行き、子が滞留していれば子の要求内容を読みに行く。順序が先に決まります。

このスクリプトがあえて見ていないものにも触れておきます。ハートビートは、作業が進んでいなくても最終イベントの時刻を新しくします。「直近に動きがある」だけでは判断材料になりません。ここで滞留と数えるのは、解決されていない承認要求だけです。

なお、このログは 8 月時点で公開されている subagent_infotool_info の形に合わせて自分で組み立てたものです。数値は実際にスクリプトを走らせて得た値ですが、製品の本番実行から採取した記録ではありません。

「always proceeds」が塞ぐのは、どの穴か

always proceeds は、サブエージェントが出したアーティファクトを自動で承認します。親が別件で止まっていても、子はそこで足を止めません。

効くのは、待ち合わせそのものが原因の停止です。承認の中身に危険があって止まっていたわけではなく、単に順番待ちだった場合、この 1 行で朝の 30 分が消えます。

深さも関係します。8 月の更新では、ネストしたサブエージェントが孫以降も表示されるようになり、どの深さでもツールの確認要求を処理できるようになりました。それ以前は、二段下で止まっていると、そもそも見ている範囲の外にいたことになります。識別子で走査する価値がいちばん出るのが、この場合です。

効かないのは、親が出しているダイアログのほうです。親の承認は依然として人間を待ちます。同じダイアログが繰り返し出て手が止まる症状であれば、原因は別のところにあります。そちらはAntigravity の許可ダイアログが何度も表示される・「常に許可」が効かないときの診断と解決で扱いました。

症状滞留スクリプトの出方手を入れる場所
子だけが止まり、親は動いている子のみ滞留子の要求内容を確認。ツール権限を見直す
親子が同時に止まっている親と子が近い秒数で並ぶ親の承認を解決する。再発防止に always proceeds
同じ確認が何度も出る滞留は出るが秒数が短く入れ替わる承認ルールの一致条件を見直す
孫以降で止まる親の欄が子の ID になっている行が残る深さごとに確認要求を処理できているか確かめる

自動承認をどこで止めるか

便利さと引き換えに、承認をひとつ手放す変更です。私はここを一律には決めませんでした。

線を引いたのは、成果物が出る先です。作業ディレクトリの中に閉じた生成物であれば、自動承認にしています。読み取りと解析、下書きの生成、比較用の一時ファイル。壊れても捨てて作り直せます。

私自身、ひとりで作っている以上、誤った成果物を止めてくれる second reviewer はいません。その役目を境界のほうに持たせています。

一方で、リポジトリの外へ出るもの、署名や配信に関わるもの、資格情報を触るものは、朝の自分に判断を残しました。夜のうちに片づく気持ちよさより、朝に取り返しがつくことのほうを取っています。

判断が難しいのは、承認ルールの一致条件が思ったより広い場合です。自動承認の範囲を決めたつもりでも、ルールの書き方によっては意図より広く通ってしまいます。私はここで一度痛い目を見ていて、その棚卸しの手順は何にも一致しないつもりの1行が、全コマンドを承認していましたにまとめました。自動承認を広げる前に、いま通っている範囲を確かめておくほうが安全です。

明日の朝のために置いておくもの

止まった朝に慌てないための備えは、三つで足ります。

  1. 滞留スクリプトを、実行ログの出力先と同じ場所に置く。朝いちばんに走らせるだけで、親と子のどちらを見るべきかが決まります
  2. subagent_infolog_uri を控える運用にする。滞留した子のログへ、探さずに飛べます
  3. 自動承認の範囲を、成果物の出る先で書き出しておく。判断を毎回やり直さずに済みます

止まっている場所を最初に取り違えると、健全なほうのログを 20 分読むことになります。私はそれをやりました。次の朝は、まず親を見てください。

エージェントに夜を任せる運用は、うまく回れば静かで気持ちのいいものです。この記事が、その静けさを取り戻す助けになれば嬉しく思います。

シェア

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

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

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

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

関連記事

Agents & Manager2026-05-20
Antigravity エージェントのプロンプトキャッシュとコンテキスト戦略 — 長期運用で月額APIコストを6〜8割削る実装パターン
長く回し続けるエージェントほど、月末の請求は地味に効いてきます。個人開発で5,000万DL規模のアプリ事業を回しながらAdMob収益と並走させてみたところ、プロンプトキャッシュとコンテキスト戦略の組み合わせで API 費用を 6〜8 割削減できた実装の手応えがありました。本番運用で実際に使っているキャッシュ階層・コンテキスト圧縮・TTL 設計を、コードと数値でまとめました。
Agents & Manager2026-04-28
Antigravity エージェントの月額コストが読めなくなる前に — 個人開発者のための試算と削減プレイブック
気付くと月額が桁違いに膨らんでいる、というのがエージェント運用で最初に踏む大きな罠です。個人開発者として実際に試算と削減を繰り返してきた経験から、コストが暴れ出す前にやっておきたい設計と、月次の見直しサイクルを具体的な数字とコードで共有します。
Agents & Manager2026-08-07
変更したファイルからテストを選ばせたら、9 割が「該当なし」でした — import グラフで届く範囲を 3 リポジトリで測る
変更ファイル起点でテストを選ばせたところ、9割の変更で対象が0本になりました。import グラフの到達範囲を3つのリポジトリで実測し、バレルファイルが経路を広げる問題や、選択契約を組み直すまでの過程を、実測データを添えた記録としてまとめています。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →