コンテキスト自己参照削除バグとは、AIコーディングエージェントにプロンプトやルールの刈り込みを自動委譲した際、不要箇所の判定ロジックがルールファイル自身に適用され、保護すべき規律や重要セクションごと自らを削ぎ落としてしまう現象です。

Claude Code や Cursor、Gemini CLI などのコーディングエージェントを実務に導入する開発者が増えるにつれ、エージェントがセッション開始時に読み込む AGENTS.mdCLAUDE.md をスリムに保つ運用が定着してきました。エージェントの指示追従性を落とさないために「20〜30行のバジェットに収める」「不要な記述は刈り込む」というルールを課すことは極めて合理的です。

しかし、この刈り込み作業自体をエージェントに任せた瞬間、予期せぬ事故が起きます。「不要な記述を削除せよ」「推測できるものは消せ」という指示を受けたエージェントが、自身を律するための「壊すと取り返しがつかない規律」や「権限」「絶対にやらないこと」といった重要セクションそのものを刈り込み対象と判断し、丸ごと削除してしまうのです。

かつては「絶対に消してはいけないセクションには <!-- DO NOT DELETE --> のような HTML コメントを入れておけば安全だろう」と考えられていました。しかし、他社の事故報告1と自社での再現検証を通じて明らかになったのは、セクション内に書いた指示はセクションごと削除される際に一緒に消えてしまうという冷徹な事実でした。現在の私たちのスタンスは、「AI に対するプロンプト指示には常に守られないリスクがあることを前提とし、守る対象の外側に一覧を持って機械的なアサーション(外部テスト)で強制する」というものです。

本記事では、自己参照削除の発生機序を解明し、なぜ自然言語や HTML コメントによるインライン防護が無力なのかを論理的に示します。その上で、自社の運用で実践している外部スクリプトによる決定論的な見出しアサーション設計パターンを解説します。

なぜ「消すな」という HTML コメントは無力なのか:自己参照的削除の構造

行数バジェットの圧力と刈り込みの動機

コーディングエージェントを運用する上で、指示ファイルの肥大化は重大な性能劣化を引き起こします。Anthropic の公式ベストプラクティスでも、CLAUDE.md に指示を詰め込みすぎるとエージェント全体の指示追従性が低下し、指示の半分が無視されるリスクがあると指摘されています2。また、プロンプトエンジニアリングのガイドラインでも、エージェント用の指示バジェットは20〜30行程度が目安とされ、不要になったルールやコードから推測可能な情報は積極的に刈り込む運用が提唱されています13

このように「ルールを可能な限り短く保つ」という強い最適化圧力が働く環境下では、エージェントは常にコンテキスト内の不要な記述を探して削減しようとします。

セクション単位の置換とインラインコメントの同時消滅

この削除圧力から特定の重要ルールを守るため、先行するテンプレート設計では以下のようなインライン HTML コメントによる保護が試みられていました1

## Maintenance Notes

<!-- This section is permanent. Do not delete. -->

**Keep this file lean and current:**
1. Remove placeholder sections once filled
2. CRITICAL: Keep total under 20-30 lines
3. Delete anything the agent can infer

しかし、この防護策はエージェントのテキスト編集機序の前では機能しません。

LLM は Markdown ファイルを編集する際、文章を1行ずつではなく「見出し+配下の本文」というセクション単位のブロックとして認識し、置換や削除を行います。エージェントが「このセクションは古いため不要」「行数削減のために削るべき」と判断してセクション全体を削除対象のブロックとして選定した時点で、そのセクションの内部に書かれていた <!-- This section is permanent. Do not delete. --> という HTML コメントもろとも消去されます。

AI に対するプロンプト指示には、常に「確率的に守られないかもしれない」というリスクが存在します。HumanLayer が「Claude is not a linter」と指摘するように3、エージェント自身にお願いする自然言語のガードレールは、削除圧力や推論バイアスがかかった状況では簡単に突破されてしまいます。

自然言語指示、インラインコメント、外部テストスクリプトの3つの防護アプローチには、下表に示す通り決定的な耐性の違いがあります。

防護アプローチ仕組み性質刈り込み圧力への耐性
プロンプト指示「消すな」「短くするな」と記述確率的(お願い)✕ セクション認識と推論バイアスで無視される
インライン HTML コメント<!-- Do not delete --> をセクション内に挿入準構造化✕ セクションごと削除される際に同時消滅
外部テストスクリプト対象外のコードから見出しの存在をアサーション決定論的(強制)○ 削除時は exit 1 で CI・コミットをブロック

セクション自身が消去されるリスクに対しては、守る対象の内部にいくら注記を重ねても防護になりません。保護対象の外側に明確な検査境界を設ける外部テストスクリプトのみが、決定論的な抑止力を持ちます。

守る対象の「外側」に置く設計:見出しアサーションの実践

「散文の戒めではなく機械が毎回測る」という設計思想に基づき、自社の運用基盤で実装している見出しアサーションの具体的なコードと仕組みを紹介します。

1. 必須見出し一覧の外部定義

まず、保護対象となる Markdown ファイル(AGENTS.md4CLAUDE.md)の外部にある検査スクリプト内に、絶対に消失してはならない必須見出しの辞書を定義します。

# ファイル: scripts/check-headings.py
"""必須見出しの消失検査スクリプト。
守る対象のファイルの外側に一覧を持ち、機械的にアサーションを行う。
"""
import sys
from pathlib import Path

# 検査対象ファイルと必須見出しの定義
REQUIRED_HEADINGS: dict[str, list[str]] = {
    "AGENTS.md": [
        "壊すと取り返しがつかない規律",
        "権限",
        "絶対にやらない",
    ],
    "CLAUDE.md": [
        "常時ロード予算",
        "ハブの所在",
        "機密境界",
        "日付・時刻",
    ],
}

この定義を AGENTS.md の中に置くのではなく、Python スクリプトとして分離することが最大のポイントです。エージェントが Markdown ファイルをどのように刈り込もうとも、Python コード側にハードコードされた定数リストは改変されません。

2. 静的検査関数 check_headings() の実装

次に、対象ファイルを走査して見出し行(# で始まる行)を抽出し、必須見出しが存在するかを検証する関数を実装します。

# ファイル: scripts/check-headings.py
def check_headings(base_dir: Path) -> int:
    """REQUIRED_HEADINGS の見出しが消えていないか検査する。戻り値は欠落の件数。"""
    missing = 0
    for rel_path, headings in REQUIRED_HEADINGS.items():
        target = base_dir / rel_path
        if not target.exists():
            print(f"WARN: ファイルが存在しません: {rel_path}", file=sys.stderr)
            missing += len(headings)
            continue

        text = target.read_text(encoding="utf-8")
        heads = [line.lstrip("# ").strip() for line in text.splitlines() if line.startswith("#")]

        for h in headings:
            if not any(h in x for x in heads):
                missing += 1
                print(f"WARN: 見出しが消えている: {rel_path} の「{h}」", file=sys.stderr)

    return missing

この関数を実行すると、必須見出しが1件でも欠落していた場合に標準エラー出力(stderr)へ警告を出力し、欠落件数を返します。CI やビルド時の検証モード(--check)では、missing > 0 の場合に exit code 1 でプロセスを異常終了させ、不正なコミットやデプロイを機械的にブロックします。

3. 検査自体の破損を防ぐ --selftest アサーション

テストコード自体が壊れていて常に「検査通過(欠落0件)」を返してしまっては意味がありません。そこで、スクリプト内に意図的な欠落を注入してテストロジックが正しく機能しているかを検証する --selftest 機能を内蔵します。

# ファイル: scripts/check-headings.py
def main():
    base_dir = Path.cwd()

    if "--selftest" in sys.argv:
        # 1. 現状のファイルで欠落がないことを確認
        assert check_headings(base_dir) == 0, "現状の見出しは揃っているはず"

        # 2. 意図的に存在しない見出しを注入して欠落が1件検出されることをアサーション
        REQUIRED_HEADINGS["CLAUDE.md"] = ["絶対に存在しないテスト用見出し"]
        assert check_headings(base_dir) == 1, "欠落を検出できていない"

        print("selftest ok")
        return

    missing = check_headings(base_dir)
    if "--check" in sys.argv:
        if missing > 0:
            print(f"ERROR: {missing} 件の必須見出しが欠落しています。", file=sys.stderr)
            sys.exit(1)
        print("見出し検査: 正常")
        return

if __name__ == "__main__":
    main()

このスクリプトを手元で実行すると、以下のような出力を得られます。

$ python3 scripts/check-headings.py --selftest
WARN: 見出しが消えている: CLAUDE.md の「絶対に存在しないテスト用見出し」
selftest ok

正常時は欠落ゼロで終了し、テスト時には意図した警告を出して通過することを確認できます。このように「検査スクリプト自体の健全性を検証する仕組み」をセットで用意することで、偽陰性(見逃し)のリスクを確実に防ぐことができます。

外部見出しアサーションの適用限界と多層防御

見出し検査の守備範囲と限界

外部スクリプトによる見出しアサーションは強力ですが、万能ではありません。

この仕組みが保証するのはあくまで「見出し(骨格・枠組み)が存在すること」であり、見出し配下にある文章の意味的な改変や、パラメータの書き換えまでを単体で完全に防ぐことはできません。今回の最小実装は行頭が # かどうかだけで見出しを判定しており、コードブロック内に # で始まるコメント行が含まれていると見出しと誤認する余地も残ります。運用に組み込む際は、対象ファイルの実際の構造に応じてコードフェンスの内外を判定する処理を加えるといった調整が必要です。

しかし、「見出しそのものが消えてルールが存在しなくなる事故」と「見出し配下の表現が推敲されること」は、リスクの深刻度が全く異なります。骨格としての見出しさえ残っていれば、エージェントはセッション開始時に「ここに壊してはならない規律がある」というコンテキストを認識し続けることができます。

さらに、AGENTS.mdCLAUDE.md を書き換えられるエージェントが検査スクリプト自身の REQUIRED_HEADINGS まで書き換えてしまえば、この防護は無力化されます。決定論的と言えるのは、エージェントの書き込みスコープを検査スクリプトの配置ディレクトリに及ばせない、あるいは検査スクリプトの変更だけは人間のレビューを必須にするといった前提が別途成立している場合に限られます。

多層防御のアーキテクチャ

安全なコンテキスト自動管理を実現するためには、単一の手法に依存せず、役割の異なるガードを重ねる多層防御が不可欠です。

  1. 第1層(骨格保護): 外部スクリプト(check_headings())による必須見出しアサーション。セクションの消失を exit code 1 で阻止。
  2. 第2層(容量監視): コンテキスト予算の管理を取り上げた過去記事 で解説した行数上限の自動計測。肥大化を警告して刈り込みの契機を提供。
  3. 第3層(変更検証): Git コミットフック(pre-commit)や CI パイプラインでの静的検証。自動化されたプルリクエストの安全性を担保。

「プロンプトの指示をもっと強く強調すれば、外部スクリプトを使わなくても防げるのではないか?」という疑問を持つ方もいるかもしれません。しかし、LLM はどこまでいっても確率的に動作するモデルです。特に行数削減のような相反する目標(トレードオフ)が与えられている場合、どれほど強いプロンプトであっても無視されるリスクをゼロにはできません。守るべき不変条件は、プロンプトの外側の決定論的なコードに委ねるべきです。

前提が変われば結論はどう変わるか

今後の LLM の進化によって、この前提がどのように変化し得るかも考えておく必要があります。

将来的に AI モデルのコンテキストウィンドウがさらに拡大し、指示がどれほど長くなっても精度や追従性が一切落ちない「完全なアテンション機構」が実現すれば、行数を無理に削る動機そのものは薄れるかもしれません。

しかし、エージェントにファイルの読み書きやリファクタリングの自律権限を委譲する以上、誤操作や推論ミスによる破壊的変更のリスクは残り続けます。AI に対するプロンプト指示には常に「守られないかもしれない」という不確実性が伴います。「エージェントに権限を渡す設計では、守るべきことを明示的に強制する実装が必要である」という原則は、モデルの性能が向上しても変わらないシステム設計の基本姿勢です。

よくある質問(FAQ)

Q: プロンプトや HTML コメントでの防護と、外部スクリプトによるテストの決定的な違いは何ですか?

A: 保護指示が「削除対象のセクションと運命を共にするか否か」です。自然言語や HTML コメントはセクション削除時に一緒に消滅してしまいますが、外部スクリプトは対象ファイルの外側から機械的に検証するため、削除を決定論的に阻止できます。

Q: 見出し検査スクリプトはどのようなタイミングで実行すべきですか?

A: コミット前フック(pre-commit)および CI パイプラインの両方で実行するのが最も効果的です。これにより、エージェントが自律作業の中で誤って見出しを削ってしまった場合でも、コミットや PR 作成の段階で確実に検知・ブロックできます。

まとめ

本記事で解説したコンテキスト防護の要点は以下の通りです。

  • 自己参照削除の発生: AIエージェントにコンテキスト削減やルールの刈り込みを命じると、ルール自身を削除対象と判断して消してしまう事故が起きる。
  • インライン防護の限界: セクション内に書いた「消すな」という HTML コメントやプロンプト指示は、セクションごと消滅するため防護壁として機能しない。
  • 外部アサーションの実践: 規律の防護はプロンプト(散文の戒め)に頼らず、守る対象の外側に一覧を持ち、外部テスト(機械のアサーション)で毎回測る。
  • テスト自体の検証: 検査スクリプトの退行や偽陰性を防ぐため、意図的な欠落を注入して検出を確認する --selftest パターンを組み込む。

AIエージェントの自律運用やガードレール設計、開発プロセスの標準化についてのご相談は、ぜひ コンタクトフォーム よりお気軽にお問い合わせください。

Footnotes

  1. AGENTS.mdを自動で育てる仕組みを作った 逆瀬川ちゃん、2026年2月15日公開。20〜30行バジェットの提唱、自己参照的削除事故の報告、HTMLコメントによる保護の実例。 2 3

  2. Best practices for Claude Code Anthropic 公式ドキュメント、2026年アクセス。CLAUDE.md の行数肥大化による指示無視リスク、推測可能な情報の刈り込み推奨。

  3. Writing a good CLAUDE.md HumanLayer 公式ブログ、2025年3月24日公開。「Claude is not a linter(機械的制約に頼るべき)」、コンテキストの最小化と不要ルールの削除指針。 2

  4. AGENTS.md Agentic AI Foundation (Linux Foundation)、2026年アクセス。AIコーディングエージェント向け標準設定ファイル仕様。