Skip to main content
技術

CLAUDE.mdのルールが守られない3つの理由——AIエージェントの「優先度」設計を見直した話

CLAUDE.mdにルールを書いたのに、AIが従わない場合がある。その原因は「無視」ではなく「優先度の低さ」にある。命令競合・汎用的すぎる記述・コンテキスト埋没の3パターンを解説し、実際に効いた対処法をまとめた。

CLAUDE.mdのルールが守られない3つの理由——AIエージェントの「優先度」設計を見直した話

はじめに

CLAUDE.mdのルールが守られないという体験は珍しくない。「featureブランチで作業すること」と書いたのにmainに直接コミットされた。「anyは禁止」と書いたのに平然とanyが生成された。会話の中でルールの内容を確認させると、ちゃんと把握している。「知っている」のに「やらない」。このズレがCLAUDE.mdへの不信感を生む。

調べるうちに問題の本質が見えた。AIがルールを無視しているのではなく、ルールの優先度が相対的に低い状態になっていることが原因だ。この記事では、CLAUDE.mdが機能しなくなる3つのパターンと実際に改善した方法を解説する。

AIがルールを「無視」するのではなく「優先度が低い」だけだ

LLMはコンテキストウィンドウに含まれるすべての情報を「知っている」状態で推論する。しかし「知っている」ことと「それを優先する」ことは別問題だ。

AIエージェントの指示優先度は、指示の物理的な存在ではなく、他の指示との相対的な重みで決まる。

Claude Codeは起動時に独自のシステムプロンプトを持つ。そこにはツールの使い方・安全ガイドライン・デフォルト挙動など約50件の内部指示が含まれている。CLAUDE.mdの内容はその上に積まれる形で処理されるため、最初から「上位」ではない。

複数の指示が競合したとき、必ずしもCLAUDE.mdが勝つわけではない。

命令密度にも限界がある

CLAUDE.mdが命令密度500行に達している。フロンティアモデルが確実に従える命令数はおよそ150〜200件とされている。Claude Codeのシステムプロンプトがすでに約50件を消費しているとすると、CLAUDE.mdに使える実効スロットは100〜150件程度だ。

500行を超えるCLAUDE.mdは命令密度が高くなりすぎて、大半の記述が「読まれているが従われない」状態に陥る。命令が多いほど安全という直感は誤解だ。指示の総量が増えるほど、残ったルールの信号密度は下がるのだ。

CLAUDE.mdが機能しなくなる3つのパターン

パターン1:指示が競合している

CLAUDE.mdにおける指示競合は最も見落とされやすい問題だ。「迅速に作業を進めること」と「featureブランチで作業すること」が同一ファイルに共存していた時期がある。小さな修正のとき、AIは「迅速さ」を優先してブランチを切らずにmainへコミットした。

競合は明示的な矛盾だけではない。「コードはシンプルに保つこと」と「型安全性を徹底すること」は一見矛盾しないが、実装の文脈によってどちらが「シンプル」かの判断が分かれる。優先順位が明示されていない複数の指示では、AIが文脈に応じて取捨選択する。その判断が設計者の意図と一致しない場合、「ルールが守られていない」という状況が生まれるのだ。

パターン2:ルールが汎用的すぎる

「コードはシンプルに」と書いていた時期がある。結果として型定義を省略したanyが生成され、TypeScriptのstrictモードでエラーになった。「シンプル」の定義をモデルのデフォルト解釈に委ねると、型定義の省略が「シンプル化」と判断されてしまう。

禁止事項は肯定表現より伝わりやすい。「コードはシンプルに」より「anyは禁止。型が不明な場合はunknownを使うこと」の方が指示として明確だ。ルールを書くとき「AIのデフォルト挙動のうち何を止めたいか」を起点にすると精度が上がる。

やるべきことではなく、やめさせたいことから書く習慣が大切だ。

パターン3:命令が多すぎてコンテキスト上で埋もれる

LLMにはコンテキストの冒頭と末尾への注意が集中しやすいというバイアスがある(プライマシー効果・リーセンシー効果)。CLAUDE.mdの中間部分に書かれたルールは相対的に参照されにくいのだ。

有効な対策はCLAUDE.mdを分割することだ。.claude/rules/配下に機能別ファイルを分けて、@参照でインクルードする方式を採用する。

# CLAUDE.md(本体はシンプルに保つ)
@.claude/rules/git.md
@.claude/rules/typescript.md
@.claude/rules/workflow.md

分割することで各ルールファイルが独立したコンテキストとして読み込まれる。コンテキスト上の埋没リスクが下がり、「どのルールをいつ参照させるか」の設計が容易になる。

タスク固有のルールはCLAUDE.mdの本体に置かず、必要なタイミングで注入する構造が理想である。

実際に変えた3つのこと

1. ルールに「なぜ」を1行添える

理由のないルールはモデルが「例外があるかもしれない」と判断するすき間を生む。「迅速さ」と「ブランチ遵守」が競合したとき、理由がなければ文脈依存の判断になる。理由を添えることで指示の重みが上がるのだ。

# 修正前
- squash mergeのみ使うこと

# 修正後
- マージ方式はsquash mergeのみ(merge commit / rebase merge禁止)
  - squash merge後はローカルmainとリモートがdivergeするためhard resetが必要になる

「なぜそのルールが存在するか」をモデルが理解すると、ルールの適用範囲を正確に推論しやすくなる。「このケースは例外か」という判断をモデルに委ねるのではなく、制約の根拠を示すことが判断の精度を高める。

2. 競合する指示を整理・削除する

CLAUDE.mdの全ルールを並べて「この2つは矛盾しないか」を確認した。曖昧な方針(「迅速に進めること」など)は削除した。AIがデフォルトで正しく判断できることはルールから外すことで、残ったルールの信号密度が上がる。

ルールの数を減らすことへの抵抗感は理解できる。しかし少ないルールが確実に守られることは、多いルールが不定期に守られることより運用として安定している。「ルールを増やす」のではなく「効くルールに絞る」という発想の転換が必要だ。

3. Hooksで「外れようがない構造」にする

Claude Code Hooksの設定は、ルールをコード実行レベルで強制する仕組みだ。「mainへの直接コミットを禁止する」というルールをCLAUDE.mdに書くだけでなく、Hooksでコミット前にブランチを検査する処理を挟む。AIがうっかり指示を外れた場合でも物理的にブロックできる。

// .claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node scripts/hooks/check-branch.mjs"
          }
        ]
      }
    ]
  }
}

check-branch.mjsの中でカレントブランチを確認し、mainだった場合にexit 1を返す。ルールを「AIに守らせる」のではなく「構造的に外れないようにする」発想の転換である。

CLAUDE.mdが扱うのは判断の指針であり、重要な制約はHooksやCI/CDで二重に担保することが堅牢な設計だ。

関連記事

まとめ

問題パターン

原因

対処

指示が競合している

モデルが文脈依存で優先度を判断する

矛盾する指示を整理・削除する

ルールが汎用的すぎる

解釈の余地が広い

禁止表現・具体例に書き換える

命令が多すぎる

コンテキスト中間部が埋没する

.claude/rules/に分割して@参照

ルールの重みが低い

理由がなく例外を許容されやすい

「なぜ」を1行添える

AIの判断に委ねすぎ

ルールが指示止まり

HooksでCLIレベルで強制する

CLAUDE.mdはAIへの「お願い」ではなく、動作設計の一部である。競合を排除し、密度を保ち、構造で担保する。この3軸で見直すことで、「知っているのにやらない」問題のほとんどは解消できる。