ANTIGRAVITY LABEN
記事一覧/Antigravity 基本
Antigravity 基本/2026-09-15中級

エージェントが理由を残さず終わるとき、読ませたページの文字コードを疑っています

ReadURL に渡したページが原因で、Antigravity のエージェントが Agent execution terminated だけを残して終わることがあります。文字コードを宣言しない古いサイトに当たったときの切り分けと、先に取得して渡す運用への切り替え方を書き残します。

antigravity456readurlencoding2troubleshooting89agents99

受託で引き継いだサイトの仕様を調べていた午後のことでした。社内向けに公開されている古い技術資料のページを三つ並べて、エージェントに順番に読ませていました。一つめは通り、二つめも通り、三つめで会話がふつりと途切れました。

画面に残ったのは Agent execution terminated due to error の一行だけです。何を読もうとしたのか、どこで止まったのか、再試行すれば直るのか——手がかりは何もありませんでした。

最初のうち、私は自分の頼み方を疑っておりました。指示が長すぎたのか、同時に渡したファイルが多すぎたのか。プロンプトを短くして投げ直し、資料を一つずつに分け、それでも同じところで終わります。半日かけてようやく気づいたのは、落ちているのは私の指示ではなく、読ませた三つめのページのほうだということでした。

落ちたのは指示ではなく、ページのほうでした

原因は、そのページが文字コードを HTTP ヘッダで宣言していないことにありました。

いまも報告が続いている事象で、Antigravity CLI のリポジトリに issue #818 として上がっております。Content-Typecharset を持たず、HTML の <meta charset=iso-8859-1> だけで名乗っているようなページを ReadURL に読ませると、デコードの失敗が握りつぶされずにエージェントの実行そのものを終わらせてしまう、という内容です。9月15日時点で未解決のままで、日本語で書かれた説明も見当たりません。

ここが日本で作業している方に効いてきます。charset ヘッダを付けずに Shift_JIS や EUC-JP のまま配信されているページは、いまも珍しくありません。官公庁の古い告示、企業の製品仕様ページ、十年以上更新されていない技術資料。受託で古いサイトを引き継ぐと、まず間違いなくこの種のページに当たります。

つまり日本語圏で調べ物をしている方ほど、この落ち方に当たりやすいのです。にもかかわらず、画面には文字コードの「も」の字も出てきません。

落ちるページかどうかは、渡す前に分かります

疑い方さえ決めておけば、切り分けは一分で終わります。見るのは二箇所だけです。

まず HTTP ヘッダに charset があるかどうか。

# ヘッダだけ取得して Content-Type を見る
curl -sI https://example.com/old-spec.html | grep -i '^content-type'

返ってくるのがこの形なら安全です。

content-type: text/html; charset=UTF-8

危ないのはこちらです。charset がどこにもありません。

content-type: text/html

charset が無いときは、本文の先頭だけ取って meta を確かめます。

# 先頭 2KB だけ取得して meta charset を探す
curl -s --range 0-2047 https://example.com/old-spec.html | grep -io 'charset=[a-z0-9_-]*'

ここで charset=shift_jischarset=euc-jp、あるいは charset=iso-8859-1 が出てきたら、そのページは渡す前にひと手間かけたほうが安全です。逆に何も出てこない場合——ヘッダにも meta にも宣言が無いページ——は、いちばん静かに落ちます。

ヘッダに charset がないとき、受け取る側は何を UTF-8 と見なすかを自分で決めなければなりません。HTTP の歴史的な既定では text/* は ISO-8859-1 として扱われます。日本語のバイト列を ISO-8859-1 として読みにいけば、当然どこかで破綻します。握りつぶされずに例外が上がると、その例外はエージェントの実行そのものを道連れにします。

先に取得して、UTF-8 に直してから渡します

見分けがついたら、次はどう渡すかです。私は「怪しいページはエージェントに直接読ませず、手元で UTF-8 に直してからファイルとして添付する」という形に落ち着きました。

下のスクリプトは、URL を一つ受け取って、ヘッダ・meta・日本語の主要な符号化方式の順に候補を試し、成功したものを UTF-8 で書き出します。標準ライブラリだけで動きます。

#!/usr/bin/env python3
"""URL の中身を UTF-8 に直して保存する。
 
エージェントへ直接 URL を渡す前のひと手間として使います。
出力されたファイルを添付すれば、デコードはこちら側で完了しています。
"""
import re
import sys
import urllib.request
 
# 日本語圏で実際に当たる順に並べています。
# cp932 は Shift_JIS の上位互換なので、Shift_JIS より先に置きます。
FALLBACKS = ["utf-8", "cp932", "euc-jp", "iso-2022-jp", "latin-1"]
 
 
def detect_declared(headers, head_bytes):
    """HTTP ヘッダと meta タグから宣言された charset を拾う。"""
    ctype = headers.get("Content-Type", "")
    m = re.search(r"charset=([\w-]+)", ctype, re.I)
    if m:
        return m.group(1).lower(), "header"
 
    # meta は ASCII 範囲に収まるので latin-1 で安全に走査できます
    text = head_bytes.decode("latin-1", errors="replace")
    m = re.search(r'charset=["\']?([\w-]+)', text, re.I)
    if m:
        return m.group(1).lower(), "meta"
    return None, "none"
 
 
def fetch_as_utf8(url, out_path):
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=30) as res:
        raw = res.read()
        declared, source = detect_declared(res.headers, raw[:2048])
 
    print(f"declared={declared or '(なし)'} source={source} bytes={len(raw)}")
 
    # 宣言があればそれを最優先で試し、失敗したら候補を順に当てます
    candidates = ([declared] if declared else []) + FALLBACKS
    for enc in candidates:
        try:
            # errors は指定しません。黙って壊れた文字を残さないためです
            text = raw.decode(enc)
        except (UnicodeDecodeError, LookupError):
            continue
        with open(out_path, "w", encoding="utf-8") as f:
            f.write(text)
        print(f"✅ {enc} でデコードし {out_path} へ保存しました")
        return 0
 
    print("❌ どの符号化方式でも読めませんでした。バイナリの可能性があります")
    return 1
 
 
if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("usage: fetch_utf8.py <URL> <出力ファイル>")
        sys.exit(2)
    sys.exit(fetch_as_utf8(sys.argv[1], sys.argv[2]))

実行するとこのように出ます。

$ python3 fetch_utf8.py https://example.com/old-spec.html spec.txt
declared=shift_jis source=meta bytes=48213
✅ cp932 でデコードし spec.txt へ保存しました

一点だけ、意図して外している作法があります。decode()errors="replace" を渡していないことです。

置換を許すとスクリプトは必ず成功します。そのかわり、読めなかった文字が U+FFFD の四角として本文に残ります。エージェントに渡す資料で、いちばん困るのは「落ちること」ではなく「壊れたまま通ること」でした。 落ちれば気づけますが、四角が混じった仕様書を読ませると、エージェントは欠けた箇所を推測で埋めてきます。そしてその推測は、こちらが疑わないかぎり最後まで通ってしまいます。

日本語ファイルをエージェントに編集させる場面でも同じ落とし穴があり、日本語を含むファイルをエージェントに編集させる前に、バイト列で確かめている3つの検査にそのときの運用を書いております。読み込みと書き出しで、疑うべきところが対称になっているのが面白いところです。

無人で回す処理では、渡す前に落とします

ここからは、定刻に無人で走らせている処理に組み込むときの話です。

私は Lab のサイト群と WordPress のブログを自分で回しており、その多くが決まった時刻に無人で動いております。壁紙アプリの素材を配信前に点検する処理も同じ枠に乗せており、個人開発でこれだけの数を抱えると、人が見ていない時間帯のほうが長くなってまいります。無人の処理で困るのは、失敗そのものよりも失敗が何の音も立てずに起きることでした。

ReadURL でエージェントごと落ちる構成は、まさにその形をしています。ログには終了だけが残り、何を読もうとして落ちたのかは残りません。

ですので、URL を扱う自動処理では、エージェントに渡す前段で弾く形にしました。先ほどのスクリプトを前置きに使うだけです。

#!/usr/bin/env bash
# 取得できたものだけをエージェントへ回す
set -u
URL="$1"
OUT="/tmp/fetched_$(date +%s).txt"
 
if python3 fetch_utf8.py "$URL" "$OUT"; then
  agy -p "$OUT を読んで、仕様の変更点を箇条書きで3点にまとめてください"
else
  # 落ちた事実と URL を残してから終えます
  echo "$(date '+%F %T') SKIP decode-failed $URL" >> fetch_failures.log
  exit 0
fi

大事なのは exit 0 のほうです。デコードに失敗したページ一つのために、その日の処理全体を失敗扱いにする必要はありません。読めなかったという事実だけを残し、処理は先へ進めます。 この形にしてから、翌朝ログを見れば何が読めなかったかが分かるようになりました。

なお外部 URL の取得は、CLI 1.1.28 以降、あらかじめ許可していなければ既定で承認待ちになります。無人で走らせる構成では、落ちるより先に「止まる」ことがあり得ます。承認の境界をどこに引くかはAntigravity CLI と Claude Code の預け先を決める3つの問いに別途まとめております。

直るまでのあいだ、どう付き合うか

issue #818 は今も open のままで、修正版の予告も出ておりません。ラベルは付いているものの、開発側からの返信はまだありません。

ですので、しばらくは「こちらが先に気づく」しかないのだと思います。とはいえ、気づき方さえ決めておけば、消耗するのは最初の一回だけです。私の場合は次の三つに落ち着きました。

場面やること理由
エージェントが理由を残さず終わった直前に渡した URL の Content-Type を見る一分で切り分けられ、外れても損が小さいため
古いサイト・社内資料を読ませる先に取得して UTF-8 に直し、ファイルで添付するデコードを自分の手元で完結させられるため
無人の処理に URL 取得が入る前段で弾き、失敗した URL だけログに残す一つの失敗で全体を止めないため

振り返ってみますと、半日を溶かした原因は文字コードそのものではありませんでした。エラーが見えないとき、私は自分の側だけを疑いすぎる——そこが本当のつまずきどころだったのだと思います。いまは、手がかりが何も残っていない終わり方をしたときこそ、自分の指示ではなく「渡したもの」を先に見るようにしております。

今日のところは、いつも読ませている社内資料や古い技術ページを一つ選んで、curl -sIContent-Type を覗いてみていただければと思います。charset が書かれていなければ、それが次にあなたの手を止めるページです。

同じところで半日を溶かす方が一人でも減れば嬉しく思います。お読みいただきありがとうございました。

シェア

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

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

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

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

関連記事

Antigravity 基本2026-05-11
Antigravity で Gemma 4 のツール呼び出しが失敗するとき確認すべき3つのこと
AntigravityでGemma 4のFunction Callingが失敗する原因をパターン別に解説。システムプロンプト設計・スキーマ設計・コンテキスト長の3点から診断・修正する方法を紹介します。
Antigravity 基本2026-06-16
応答が4倍速くなったエージェントで、並行パイプラインの設計を組み直す
Antigravity CLI が高速モデルに載せ替わったとき、エージェントの並行パイプラインで律速になる場所は移動します。検証・粒度・並行度・コスト上限を、速度を前提に組み直すための実践的な判断軸をまとめました。
Antigravity 基本2026-05-31
Antigravity のエージェントが途中で 429 RESOURCE_EXHAUSTED と出して止まるときの原因と対処
Antigravity でエージェントに長いタスクを任せていると、途中で「429 RESOURCE_EXHAUSTED」と出して手が止まることがあります。これは API のレート制限・クォータ超過のサインです。私が自動運用の現場で踏んだ原因の切り分けと、再発させないための具体的な対処をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます