土曜の朝、軽微な表示崩れを直すつもりでエージェントに修正を任せました。返ってきたのは「該当箇所を修正し、ビルドとデプロイが成功したことを確認しました」という、いつもどおりの完了報告です。安心して別の作業に移り、数時間後に本番ページを開いたら、記事本文がまるごと空でした。HTTP ステータスは 200。ビルドログにもエラーはありません。それでも、読者が見ていたのは中身のないページでした。
この種の事故が厄介なのは、エージェントの報告がすべて事実だという点です。コードは直っている、ビルドは通っている、デプロイも完了している。嘘は一つもありません。ただ「直したコードが、本番URLで読者の画面にちゃんと描画されているか」だけが、誰も確認していなかったのです。今日はこの最後の一歩を、Antigravity 2.0 の Browser Sub-Agent に肩代わりさせる完了ゲートの作り方を残します。
ビルド成功と「ページが見える」は別の保証である
まず押さえておきたいのは、デプロイ成功が描画成功を意味しないという事実です。私が遭遇した「本文が空の 200 ページ」には、いくつかの典型的な経路があります。
サーバーサイドレンダリングの途中で例外が起きても、フレームワークによってはストリーミング済みのレスポンスを 200 のまま返し、本文だけがエラー境界の表示に差し替わります。静的アセットの取得が一瞬失敗すると、本文が空のまま生成されたページがそのまま配信されることもあります。エッジキャッシュが、その瞬間に生成された壊れたHTMLを掴んでしまうと、しばらく固定化されます。いずれもビルドは成功しているので、ビルドログを見るかぎり何も問題は見つかりません。
エージェントが参照しているのは、たいていこのビルドログとデプロイAPIの応答です。つまりエージェントは「成功した」という信号だけを根拠に完了を宣言しています。私たちが本当に確認したいのは、その先――公開URLを実際に開いたとき、本文を載せる要素の中に文字が入っているか――なのですが、そこはエージェントの視界の外にありました。
完了の定義を「本番URLで主要セレクタが埋まっていること」に置き換える
解決の方向はシンプルです。エージェントの「完了」の定義を、ビルド成功から一段進めて、本番URLの描画確認まで含めてしまいます。Antigravity 2.0 の Browser Sub-Agent は、メインのエージェントとは別に実際のブラウザを操作できるので、この確認を本人にやらせられます。
私が AGENTS.md(プロジェクト直下のエージェント向け指示ファイル)に書いている完了条件は、おおよそ次のような文面です。
## 完了の定義(デプロイを伴う変更)
ビルド・デプロイの成功だけでは「完了」と見なさない。
以下をすべて満たして初めて完了を報告すること。
1. Browser Sub-Agent で本番URL(該当ページ)を開く
2. 本文コンテナ `.article-content` が存在し、
テキスト長が 200 文字以上であることを確認する
3. ページ内に `data-error-boundary` 属性を持つ要素が
存在しないことを確認する
4. 上記が満たせない場合は「完了」と書かず、
観測した状態(空本文・エラー境界の有無)を報告するポイントは、確認すべきセレクタを具体的に名指ししている点です。「ページがちゃんと表示されているか確認して」という曖昧な指示だと、エージェントはスクリーンショットを撮って「表示されているようです」と返してきます。空ページもスクリーンショット上は「白い画面が表示されている」状態なので、これでは見抜けません。本文を載せる要素を名前で指定し、その中に十分な長さのテキストがあることを条件にすると、空ページは確実に弾けます。
Browser Sub-Agent に渡す確認スクリプト
セレクタの存在と中身の長さは、Browser Sub-Agent のコンソール実行で機械的に判定できます。私はエージェントに、本番URLを開いたあと次の小さなスクリプトを評価させ、その戻り値を完了判定の根拠にしています。
// 本番URLを開いた状態で評価する
(() => {
const main = document.querySelector('.article-content');
const text = main ? main.innerText.trim() : '';
const hasErrorBoundary =
document.querySelector('[data-error-boundary]') !== null;
const htmlClosed = document.documentElement.outerHTML
.includes('</html>');
return {
selectorFound: !!main,
textLength: text.length,
hasErrorBoundary,
htmlClosed,
verdict:
!!main && text.length >= 200 &&
!hasErrorBoundary && htmlClosed
? 'RENDERED'
: 'EMPTY_OR_BROKEN',
};
})();verdict が RENDERED のときだけ完了を認め、EMPTY_OR_BROKEN が返ったらエージェントは修正フェーズに戻ります。hasErrorBoundary を別フラグにしているのは、原因の切り分けのためです。エラー境界が出ているなら描画中の例外、本文が空なのにエラー境界も無いならアセット取得の失敗、というように、戻り値を見ただけで次に疑う場所が絞れます。
なぜ「セレクタが存在する」だけでなく「テキスト長が一定以上」まで条件にするのか。要素そのものは生成されているのに中身だけ空、という壊れ方が実際に多いからです。.article-content という箱はあるのに、その中に一文字も入っていない。セレクタの有無だけを見ていると、この状態をすり抜けてしまいます。長さのしきい値は記事の最小ボリュームに合わせて決めれば十分で、私は本文系のページで 200 文字を下限にしています。
デプロイ直後の「まだ古い版が見える」を避ける
ここで一つ落とし穴があります。デプロイ完了の直後に本番URLを開くと、エッジにまだ前の版がキャッシュされていて、修正前のHTMLを確認してしまうことがあります。これだと「描画は成功しているが、確かめているのは古いコード」という、もっとも見抜きにくいすり抜けが起きます。
私が使っている回避策は、デプロイのたびに更新する版を示す値――たとえば HTML 内に埋め込んだデプロイ識別子――を、確認スクリプトの一部として一緒に照合することです。今回デプロイした識別子と、本番URLから読み取れた識別子が一致して初めて「新しい版を見ている」と確定できます。識別子を持たないサイトなら、修正で必ず変わる一文(見出しの文言や新しく追加した要素)を合言葉にして、その存在を確認条件に足すだけでも効果があります。要は「成功した」だけでなく「今回直したものが、今、見えている」を確かめる、という一点です。
完了ゲートを定着させるための運用
この仕組みは、一度 AGENTS.md に書いて終わりにすると、エージェントが急いでいるときに省略されることがあります。個人開発で複数のサイトを並行して回している私の場合は、デプロイを含むタスクを「レビューできる単位」で切る運用と組み合わせ、変更を小さく保つことでこの確認自体を軽くしています。粒度の考え方はAntigravity エージェントへの依頼は「レビューできる単位」で切る — 手戻りを減らすタスク設計に書いたとおりで、確認すべきページが1〜2枚に収まっていれば、Browser Sub-Agent の往復も短く済みます。
描画確認そのものでつまずく場合、特にシングルページアプリで本文の生成が遅れて空ページと誤認されるケースは、Antigravity の Browser Sub-Agent が SPA を空ページと誤認する原因と待機戦略で扱った待機の入れ方が役に立ちます。確認スクリプトを評価する前に、本文コンテナが現れるまで待つ一手間を挟むだけで、誤判定はかなり減ります。
万一この完了ゲートが空ページを検出したときは、いきなり原因調査に入るより、まず壊れる前の状態に戻してから落ち着いて差分を見るほうが速いことが多いです。巻き戻しの段取りはAntigravity Checkpoints & Rollback — AI 開発のバージョン管理を極める実践テクニックに整理してあります。
次の一歩
もし今、エージェントの「デプロイしました」をそのまま信じて次の作業に移っているなら、まずは AGENTS.md に「本番URLで本文セレクタにテキストが入っていることを確認するまで完了としない」の一文を足すところから始めてみてください。確認スクリプトは上のものをそのまま使えますし、セレクタ名を自分のサイトの本文コンテナに置き換えるだけで動きます。ビルドの緑色のチェックと、読者の画面に文字が見えていることは別の保証だ――この一点をエージェントの完了条件に組み込んでおくと、白いページを読者に見せてしまう事故は確実に減らせます。
最後までお読みいただき、ありがとうございました。私自身 Dolice Labs で複数のページを自動更新しているので、同じように運用している方の静かな安心材料になれば嬉しいです。