Claude CodeのCLAUDE.mdが読まれない・守られない原因と確認方法|読み込む場所と順番・@import・/context・圧縮のあと

Claude CodeのCLAUDE.mdが読まれない・守られない原因と確認方法

こんにちは、おうどんです🍜

朝。 うどん屋さんの勝手口が開く。

見習いさん「はじめまして! 今日からお世話になります!」

店長(おうどん)「……昨日もいたよね?」

見習いさん「はじめまして!」

……毎朝、はじめまして。

(場面は説明用の架空のものです)

これ、Claude Code(Anthropic の AI コーディングツール)の日常そのものなんです。 Claude Code の公式ドキュメントの最初の一文も、ざっくり言うと「セッションは毎回まっさらなコンテキスト(AI が一度に見ている情報の範囲)から始まる」です。

だから店長は、毎朝ノートを渡します。 それが CLAUDE.md(Claude Code に毎回読ませる指示ファイル)です。

  • 見習いさん=Claude(毎朝まっさらで出勤する)
  • 申し送り帳=CLAUDE.md(昨日までの約束を書いたノート)
  • 受付係=Claude Code(申し送り帳を見習いさんの机に置く係)

実はおうどんも、グローバルの CLAUDE.md から Obsidian の Vault(メモを貯めているフォルダ)を読ませて、セッションをまたいで知識を引き継いでいます。 この記事の下書きを作っている定期タスクも、その申し送り帳を読んでから仕事を始めています。

ところが、よくある悩みがこれです。

CLAUDE.md に書いたのに、読まれてない気がする。守ってくれない。

今回は、その「読まれない」「守られない」を、読み込む場所と順番から切り分けていきます。

目次

結論:まずはこれだけ

先に答えを置いておきます。まず「読まれているか」を確かめ、次に「守られる書き方か」を見直す、の2段階です。

  • /context を打って、Memory files の一覧に自分の CLAUDE.md があるか見る(なければ、そもそも読まれていない)
  • 置き場所を確かめる。起動したフォルダとその上のフォルダの CLAUDE.md は起動時、サブフォルダの CLAUDE.md は「そのフォルダのファイルを読んだとき」に読まれる
  • 読まれているのに守られないなら、具体的に・短く・矛盾なく書き直す
  • 「必ず」やらせたいことは、CLAUDE.md ではなくフックや権限設定で縛る

全部の約束を太字で書けば、守ってくれるのでは?

申し送り帳が全ページ太字の店を想像してください。 見習いさん「……どれが大事なんですか?」 強調は、少ないから効くんです。

じゃあ申し送り帳を毎朝、口頭でも読み上げれば……

それはもう、会話で毎回説明しているのと同じです。 それが面倒だから、申し送り帳があるんですよね。

なぜ守られない? CLAUDE.md は「設定」ではなく「申し送り」

最初に押さえたいのは、CLAUDE.md は命令ではなく、読んでもらう文章だということです。

memory のドキュメントには、次のように書かれています。

  • CLAUDE.md と auto memory(Claude が自分で書くメモ)は、どちらも毎回の会話の最初に読み込まれる
  • Claude はそれらを「コンテキスト」として扱い、強制される設定としては扱わない
  • CLAUDE.md の中身は、システムプロンプト(AI の土台になる指示)のあとに、ユーザーのメッセージとして届けられる
  • 読んで従おうとはするが、厳密に守られる保証はない。あいまいな指示や矛盾する指示では特に

受付係「申し送り帳、机に置いておきました」

見習いさん「読みました! 『いい感じにやる』って書いてあります!」

店長「……それは私が悪い」

……いい感じ、の範囲が店長と見習いさんで違う。

ドキュメントも、指示は確かめられるくらい具体的に書くように勧めています。 例として挙がっているのは、こんな書き換えです。

あいまい具体的
コードをきちんと整形するインデントは半角スペース2つ
変更をテストするコミットの前に npm test を実行する
ファイルを整理しておくAPI のハンドラーは src/api/handlers/ に置く

(memory のドキュメントの例を、おうどんが日本語にしたものです)

「うどんはおいしく茹でる」も、あいまい枠?

あいまい枠です。 「沸騰したお湯で12分、茹で上がったら冷水で締める」まで書けば、見習いさんは迷いません。 (茹で時間は説明用の例です。麺の袋を見てください)

読み込まれる場所と順番

次は「そもそも机に置かれているか」です。CLAUDE.md は置いた場所によって、読まれるタイミングが違います。

置き場所は4種類

種類場所用途
組織の管理ポリシーmacOS:/Library/Application Support/ClaudeCode/CLAUDE.md、Linux・WSL:/etc/claude-code/CLAUDE.md、Windows:C:\Program Files\ClaudeCode\CLAUDE.md会社全体のルール。IT 部門が配る
ユーザー~/.claude/CLAUDE.md自分用。全プロジェクト共通
プロジェクト./CLAUDE.md または ./.claude/CLAUDE.mdチームで共有。Git に入れる
ローカル./CLAUDE.local.md自分用。このプロジェクトだけ。.gitignore に入れる

(memory のドキュメントの表を、おうどんが日本語にまとめたものです。~ はホームフォルダのことです)

起動したフォルダと、その上のフォルダは「起動時」

ドキュメントによると、Claude Code は起動したフォルダと、その上にあるすべてのフォルダから CLAUDE.md と CLAUDE.local.md を読み込みます。

大事なのは次の3点です。

  • 見つかったファイルは、上書きではなく全部つなげてコンテキストに入る
  • 並び順は、ファイルシステムのいちばん上から、起動したフォルダへ向かって。起動した場所に近いものほど後ろに来る
  • 同じフォルダの中では、CLAUDE.local.md が CLAUDE.md のあとに付く

さらに、ユーザーの指示はプロジェクトの指示より前に並びます(表の順が、そのまま読み込み順です)。

見習いさん「会社の申し送り帳、店長の申し送り帳、この店の申し送り帳、自分用のメモ……全部読みました!」

店長「どれが優先?」

見習いさん「全部つなげて読んだので、全部です!」

……優先順位ではなく、ページ順。

ここが勘違いしやすいところで、どれかがどれかを上書きするわけではないんです。 2つのファイルで違うことを書くと、ドキュメントでは「どちらかを勝手に選ぶことがある」とされています。 ユーザーのルールとプロジェクトのルールがぶつかったときも同じで、「どちらに従うかは分からないので、そろえておいて」と書かれています。

じゃあ一番後ろに書いたものが勝つ、でいいのでは?

ドキュメントは「後ろに来る」とは書いていても、「後ろが勝つ」とは書いていません。 矛盾したら、見習いさんがどっちかを選ぶ。つまり、矛盾させないのが唯一の正解です。

サブフォルダの CLAUDE.md は「あとから」

もう1つの落とし穴がこれです。

ドキュメントによると、起動したフォルダより下にあるサブフォルダの CLAUDE.md は、起動時には読まれません。 Claude がそのサブフォルダのファイルを読んだときに、はじめて読み込まれます。

たとえば app/ で起動して、app/src/api/CLAUDE.md にルールを書いた場合です。

  • 起動直後:app/src/api/CLAUDE.md はまだ読まれていない
  • Claude が app/src/api/ の中のファイルを読む:ここで読まれる

見習いさん「2階の厨房の申し送り帳ですか? 2階に上がったら読みます」

店長「1階で2階の話をしてたんだけど」

見習いさん「まだ上がってないので、知りません」

……正直すぎる。

「最初の質問のときから守ってほしい」ルールなら、起動するフォルダの CLAUDE.md に書くのが確実です。

--add-dir で足したフォルダの CLAUDE.md は、そのままでは読まれない

--add-dir(作業フォルダの外のフォルダを使えるようにするオプション)で足したフォルダの CLAUDE.md は、既定では読まれません。 読ませたいときは、環境変数 CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD を 1 にします(ドキュメントより)。

CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 claude --add-dir ../shared-config

(公式ドキュメントに載っている例です。今回の検証環境では実行していません。この書き方は bash・zsh 用です)

隣の店から借りてきた食材の、申し送り帳まではついてこない。

ついてこないんです。 食材だけ借りて、隣の店のルールは知らない。見習いさんとしては筋が通っています。

確認方法:/context と /memory

「読まれているか」は、推測せずに見られます。/context が一番確実です。

  • /context:今のセッションのコンテキストの内訳が出る。Memory files の一覧に、読み込まれた CLAUDE.md とルールのファイルが並ぶ
  • /memory:CLAUDE.md・CLAUDE.local.md などの置き場所を一覧にして、選ぶとエディタで開ける。まだないファイルを選ぶと作られる。auto memory のオン・オフもここ

ドキュメントのトラブルシューティングにも、まず /context の Memory files を見て、なければ Claude には見えていない、と書かれています。

見習いさん「申し送り帳ですか? 机の上には……ないですね」

店長「じゃあ、昨日の約束を知らないのは当然か」

……犯人は見習いさんではなく、机の上。

/context って、見習いさんの机の上を写真で撮る機能?

まさにそれです。 「読んだ?」と聞くより、机の写真を見るほうが早い。

🔰 ここまで読めば今日から困らない

  • CLAUDE.md は命令ではなく、読んでもらう文章。具体的・短く・矛盾なく書く
  • まず /context の Memory files を見て、読まれているかを確かめる
  • 起動したフォルダとその上は起動時に読まれる。サブフォルダの CLAUDE.md は、そこのファイルを読んだときに読まれる
  • 複数の CLAUDE.md は上書きではなく、つなげて読まれる。矛盾させない

4つ。うどんが茹で上がるより早く読める。

茹で上がるまでに /context を1回。 それで「読まれてない」のか「守られてない」のかが分かります。

分かっただけで、うどんは茹で上がらないけどね。

うどんは、ご自分で茹でてください。

ここから先は中級者向け。読み飛ばしてもOKです。

中級:読まれない・守られない、よくある理由

/context に出てこない、出てくるのに守られない。そんなときに疑う順番です。多くは「置き場所」「書き方」「長さ」のどれかです。

1. 起動したフォルダが違う

同じリポジトリでも、ルートで起動するか、サブフォルダで起動するかで、起動時に読まれるファイルが変わります。 サブフォルダで起動すれば、ルートの CLAUDE.md は上のフォルダとして読まれます。 逆にルートで起動すると、サブフォルダの CLAUDE.md は「そこのファイルを読むまで」読まれません。

2. @ インポートの書き間違い

CLAUDE.md の中に @パス と書くと、そのファイルを取り込めます(インポート)。ドキュメントの決まりはこうです。

  • 相対パスは、作業フォルダではなく、インポートを書いたファイルから数える
  • 取り込んだファイルがさらに取り込むこともできる。最大4段(four hops)まで
  • パスにスペースがあるときは、スペースの前に \ を付ける(@Design\ Docs/api.md)。付けないと、最初のスペースでパスが終わる
  • クォートで囲んだパスは、\ の有無にかかわらず取り込まれない
  • コードスパン(` で囲んだ部分)とコードブロックの中は取り込まない。パスを書きたいだけなら `@README` のように囲む

見習いさん「『@docs/rules.md を見て』と書いてありましたが、飾りの文字でした」

店長「飾りにしたの、私だった」

……バッククォートで囲むと、ただの文字になる。

3. 外部インポートの承認を断った

プロジェクトの CLAUDE.md から、作業フォルダの外のファイル(たとえば @~/.claude/my-project-instructions.md)を取り込むと、そのプロジェクトで最初に1回、承認のダイアログが出ます。 ドキュメントによると、断ると取り込みは無効のまま、ダイアログも二度と出ません。

一方、~/.claude/CLAUDE.md のようなユーザーの CLAUDE.md の取り込みは、自分で書いたファイルとして、ダイアログなしで読み込まれます(Cowork のセッションを除く)。

見習いさん「知らない住所の申し送り帳は、1回だけ『読んでいいですか』と聞きます」

店長「朝で眠くて『いいえ』押しちゃった」

見習いさん「では、二度と聞きません」

……一度断られたら、潔く引き下がるタイプ。

4. 長すぎる・矛盾している

ドキュメントの目安は、1ファイル200行未満です。 長いファイルはコンテキストを多く使い、守られにくくなる(reduce adherence)と書かれています。 また、4 MiB を超える CLAUDE.md は読み込まれず、飛ばされます。

@ インポートで分けても、取り込んだファイルは起動時に全部読まれるので、コンテキストの節約にはなりません。 減らしたいなら、あとで出てくる paths 付きのルールで「そのファイルを触るときだけ」読ませます。

5. AGENTS.md と CLAUDE.md の両方がある

ほかの AI コーディングツール用の AGENTS.md があるリポジトリの話です。 ドキュメントによると、既定では、作業フォルダかその上に CLAUDE.md(または .claude/CLAUDE.md、CLAUDE.local.md)があると、AGENTS.md は読まれず CLAUDE.md だけが読まれます(Claude Code v2.1.277 以降の動き)。 両方読ませたいときは、CLAUDE.md に @AGENTS.md と書いて取り込むのが手軽です。

見習いさん「英語版の申し送り帳と日本語版があったので、日本語版だけ読みました」

店長「中身、別のことが書いてあるんだけど」

……2冊あったら、1冊にまとめましょう。

6. 会話でだけ伝えた指示が、圧縮のあとに消えた

長い会話は、途中で要約(コンテキストの圧縮、compaction)されます。 会話の中で「今後は〜して」と言っただけの指示は、要約の中で薄まることがあります。 残したい指示は CLAUDE.md に書きます(詳しくは上級編)。

7. 「毎回必ず」の指示を CLAUDE.md に書いている

「コミットの前に必ず lint」のように、決まったタイミングで必ず動いてほしいことは、ドキュメントでもフック(決まったタイミングで自動で動くコマンド)にするよう勧められています。 フックは Claude の判断に関係なく動くからです。

見習いさん「申し送り帳に『毎回必ず』と書いてあったので、だいたい毎回やりました」

店長「だいたい、が一番こわい」

……申し送り帳でできるのは「だいたい」まで。「必ず」は仕組みで作ります。

中級:簡略モデルで「どれが・いつ」読まれるかを確かめる

Claude Code 本体は今回の検証環境では動かせないので、ドキュメントの決まりを Python で真似した簡略モデルで、フォルダ構成から読み込み順を出してみます。

先に断っておくと、これは説明用のモデルで、Claude Code の実装ではありません。 同じフォルダの中での CLAUDE.md と .claude/CLAUDE.md の順番、ルールのファイルが CLAUDE.local.md の前か後ろかはドキュメントに書かれていないので、モデルでは仮に置いています。 管理ポリシーの CLAUDE.md、AGENTS.md、claudeMdExcludes(読みたくない CLAUDE.md を外す設定)も入っていません。

用意したフォルダ構成(home はホームフォルダの代わりです):

home/.claude/CLAUDE.md
home/.claude/rules/style.md
work/CLAUDE.md
work/app/CLAUDE.md
work/app/CLAUDE.local.md
work/app/.claude/rules/testing.md      (paths なし)
work/app/.claude/rules/api.md          (paths: src/api/**/*.ts)
work/app/src/api/CLAUDE.md

list_claude_md.py:

"""CLAUDE.md の読み込み順を真似する説明用の簡略モデル(Claude Code の実装ではない)"""
import argparse
from pathlib import Path

ap = argparse.ArgumentParser()
ap.add_argument("cwd")
ap.add_argument("--home", required=True)
ap.add_argument("--top", help="ここより上のフォルダは見ない(表示用)")
args = ap.parse_args()

cwd = Path(args.cwd).resolve()
home = Path(args.home).resolve()
top = Path(args.top).resolve() if args.top else None


def show(p):
    return p.relative_to(top).as_posix() if top else str(p)


def has_paths(rule):
    text = rule.read_text(encoding="utf-8")
    return text.startswith("---") and "\npaths:" in text.split("---")[1]


print("== 起動時に読む(この順で並ぶ)")
n = 0


def add(p, note):
    global n
    n += 1
    print(f"{n:2}. {show(p)}  ({note})")


# ユーザー
if (home / ".claude/CLAUDE.md").is_file():
    add(home / ".claude/CLAUDE.md", "ユーザー")
for r in sorted((home / ".claude/rules").rglob("*.md")):
    add(r, "ユーザーのルール")

# 上のフォルダから作業フォルダへ
dirs = [d for d in reversed([cwd, *cwd.parents]) if top is None or d == top or top in d.parents]
for d in dirs:
    for name, note in (("CLAUDE.md", "プロジェクト"), (".claude/CLAUDE.md", "プロジェクト"), ("CLAUDE.local.md", "ローカル")):
        if (d / name).is_file():
            add(d / name, note)
    rules = d / ".claude/rules"
    if d == cwd and rules.is_dir():
        for r in sorted(rules.rglob("*.md")):
            if not has_paths(r):
                add(r, "ルール・paths なし")

print()
print("== あとで読む(条件がそろったとき)")
rules = cwd / ".claude/rules"
if rules.is_dir():
    for r in sorted(rules.rglob("*.md")):
        if has_paths(r):
            print(f"  - {show(r)}  (paths に合うファイルを読んだとき)")
for p in sorted(cwd.rglob("CLAUDE.md")):
    if p.parent != cwd and ".claude" not in p.parts:
        print(f"  - {show(p)}  ({show(p.parent)} のファイルを読んだとき)")
python -X utf8 list_claude_md.py demo/work/app --home demo/home --top demo
== 起動時に読む(この順で並ぶ)
 1. home/.claude/CLAUDE.md  (ユーザー)
 2. home/.claude/rules/style.md  (ユーザーのルール)
 3. work/CLAUDE.md  (プロジェクト)
 4. work/app/CLAUDE.md  (プロジェクト)
 5. work/app/CLAUDE.local.md  (ローカル)
 6. work/app/.claude/rules/testing.md  (ルール・paths なし)

== あとで読む(条件がそろったとき)
  - work/app/.claude/rules/api.md  (paths に合うファイルを読んだとき)
  - work/app/src/api/CLAUDE.md  (work/app/src/api のファイルを読んだとき)

(今回の検証環境の Windows 10・Python 3.13.1 で実行した結果です。実際にはフォルダの絶対パスを渡しています)

読み方のポイントです。

  • work/CLAUDE.md は、起動した work/app の1つ上なので起動時に読まれる
  • work/app/src/api/CLAUDE.md は下のフォルダなので「あとで読む」
  • paths 付きの api.md も「あとで読む」。一致するファイルを読むまで入らない

簡略モデル「src/api/CLAUDE.md は、あとで読みます」

おうどん「今読んでよ」

簡略モデル「2階に上がってないので」

……作った本人にも融通をきかせない。見習いさんと同じ性格に育ちました。

このモデルで6つ出たら、本物でも6つ?

違います。本物で確かめるのは /context です。 モデルは「上は起動時、下はあとから」の考え方を目で見るためのおもちゃです。

中級:@インポートを点検する

次は、@ の書き間違いを機械で見つけるスクリプトです。取り込み先が本当にあるか、何段目か、作業フォルダの外かを出します。

これも説明用の簡略モデルです。 @ の前が行頭か空白のときだけインポートとみなす、4段の数え方を「CLAUDE.md から取り込んだファイルが1段目」とする、の2点はモデルの仮定です(Claude Code の実際の判定方法は、ドキュメントには書かれていません)。

点検した CLAUDE.md:

# App
See @README.md for overview.
- git workflow @docs/git-instructions.md
- design @Design\ Docs/api.md
- old rules @docs/old-rules.md
- quoted @"docs/quoted.md"
- 例として書いただけ `@docs/example.md`
- personal @~/.claude/my-app.md

```text
@docs/in-code-block.md
```

docs/git-instructions.md から先は、chain1.md → chain2.md → chain3.md → chain4.md と、1つずつ取り込みがつながっています。

check_imports.py:

"""CLAUDE.md の @インポートを展開して確かめる、説明用の簡略モデル(Claude Code の実装ではない)"""
import argparse
import re
from pathlib import Path

MAX_HOPS = 4  # ドキュメント: 再帰的なインポートは最大4段

ap = argparse.ArgumentParser()
ap.add_argument("file")
ap.add_argument("--home", required=True)
ap.add_argument("--top")
args = ap.parse_args()
home = Path(args.home).resolve()
top = Path(args.top).resolve() if args.top else None
cwd = Path(args.file).resolve().parent


def show(p):
    try:
        return p.relative_to(top).as_posix() if top else str(p)
    except ValueError:
        return str(p)


IMPORT = re.compile(r'(?:^|\s)@((?:\\ |[^\s`])+)')


def find_imports(text):
    found, quoted = [], []
    in_fence = False
    for line in text.splitlines():
        if line.lstrip().startswith("```"):
            in_fence = not in_fence
            continue
        if in_fence:
            continue
        line = re.sub(r"`[^`]*`", "", line)  # コードスパンは飛ばす
        for m in IMPORT.finditer(line):
            raw = m.group(1)
            if raw.startswith(('"', "'")):
                quoted.append(raw)
            else:
                found.append(raw.replace("\\ ", " "))
    return found, quoted


def resolve(raw, base):
    if raw.startswith("~/"):
        return home / raw[2:]
    p = Path(raw)
    return p if p.is_absolute() else base / p


def walk(path, hops, seen):
    text = path.read_text(encoding="utf-8")
    found, quoted = find_imports(text)
    for q in quoted:
        print(f"{'  ' * (hops + 1)}[注意] @{q} : クォートで囲むと読み込まれません")
    for raw in found:
        target = resolve(raw, path.parent)
        indent = "  " * (hops + 1)
        label = f"@{raw} -> {show(target)}"
        if hops + 1 > MAX_HOPS:
            print(f"{indent}[深すぎ] {label} ({hops + 1}段目。最大{MAX_HOPS}段)")
            continue
        if not target.is_file():
            print(f"{indent}[なし] {label}")
            continue
        outside = cwd not in target.resolve().parents
        mark = " ※作業フォルダの外(初回に承認ダイアログ)" if outside else ""
        print(f"{indent}[OK {hops + 1}段目] {label}{mark}")
        if target.resolve() not in seen:
            walk(target, hops + 1, seen | {target.resolve()})


root = Path(args.file).resolve()
print(show(root))
walk(root, 0, {root})
python -X utf8 check_imports.py demo/work/app/CLAUDE.md --home demo/home --top demo
work/app/CLAUDE.md
  [注意] @"docs/quoted.md" : クォートで囲むと読み込まれません
  [OK 1段目] @README.md -> work/app/README.md
  [OK 1段目] @docs/git-instructions.md -> work/app/docs/git-instructions.md
    [OK 2段目] @chain1.md -> work/app/docs/chain1.md
      [OK 3段目] @chain2.md -> work/app/docs/chain2.md
        [OK 4段目] @chain3.md -> work/app/docs/chain3.md
          [深すぎ] @chain4.md -> work/app/docs/chain4.md (5段目。最大4段)
  [OK 1段目] @Design Docs/api.md -> work/app/Design Docs/api.md
  [なし] @docs/old-rules.md -> work/app/docs/old-rules.md
  [OK 1段目] @~/.claude/my-app.md -> home/.claude/my-app.md ※作業フォルダの外(初回に承認ダイアログ)

(今回の検証環境の Windows 10・Python 3.13.1 で実行した結果です。実際にはフォルダの絶対パスを渡しています)

  • @chain1.md が docs/ の下で見つかっているのは、相対パスを「書いたファイルの場所」から数えているから
  • @docs/old-rules.md は、ファイルがもうない。消したルールの取り込みが残っている
  • `@docs/example.md` とコードブロックの中の @ は、一覧に出てこない(飾りの文字として飛ばした)
  • @~/.claude/my-app.md は作業フォルダの外。プロジェクトの CLAUDE.md からの取り込みなので、ドキュメントどおりなら初回に承認のダイアログが出る

check_imports「old-rules.md、もうありません」

申し送り帳「……『詳しくは別紙』の別紙が、ない」

……会社でよくあるやつ。

存在しないファイルを取り込もうとしたら、Claude Code はエラーで止まるの?

そこは今回、ドキュメントで確かめられませんでした。 止まるかどうかに関係なく、中身が読まれていないのは確かなので、見つけたら直しましょう。

中級:長さと MEMORY.md の上限を確かめる

最後の点検は長さです。CLAUDE.md は200行未満が目安、auto memory の MEMORY.md は先頭200行か25KBまでしか起動時に読まれない、の2つを数えます。

check_size.py:

"""CLAUDE.md の長さと MEMORY.md の読み込み上限を確かめる"""
import sys
from pathlib import Path

CLAUDE_MD_LINES = 200          # CLAUDE.md の目安(これを超えると守られにくくなる)
MEMORY_LINES = 200             # MEMORY.md は先頭200行
MEMORY_BYTES = 25 * 1024       # または先頭25KB(先に来たほう)

for arg in sys.argv[1:]:
    p = Path(arg)
    data = p.read_bytes()
    lines = data.decode("utf-8").splitlines()
    print(f"{p.name}: {len(lines)}行 / {len(data):,}バイト")
    if p.name == "MEMORY.md":
        kept, size = 0, 0
        for line in lines:
            size += len((line + "\n").encode("utf-8"))
            if kept >= MEMORY_LINES or size > MEMORY_BYTES:
                break
            kept += 1
        print(f"  起動時に読まれるのは {kept}行目まで")
        if kept < len(lines):
            print(f"  読まれない: {kept + 1}行目〜 ({len(lines) - kept}行)")
            print(f"  最初に読まれない行: {lines[kept]}")
    elif len(lines) > CLAUDE_MD_LINES:
        print(f"  [注意] 目安の{CLAUDE_MD_LINES}行を超えています。paths 付きのルールへの分割を検討")

試しに、230行の CLAUDE.md(BIG_CLAUDE.md)と、230行の MEMORY.md を作って数えました。

python -X utf8 check_size.py demo/work/app/CLAUDE.md demo/work/app/BIG_CLAUDE.md demo/MEMORY.md
CLAUDE.md: 12行 / 293バイト
BIG_CLAUDE.md: 230行 / 3,572バイト
  [注意] 目安の200行を超えています。paths 付きのルールへの分割を検討
MEMORY.md: 230行 / 8,524バイト
  起動時に読まれるのは 200行目まで
  読まれない: 201行目〜 (30行)
  最初に読まれない行: - [メモ201](memo201.md) — 説明

(今回の検証環境の Windows 10・Python 3.13.1 で実行した結果です。実際にはフォルダの絶対パスを渡しています)

  • CLAUDE.md の200行は「目安」です。201行目から読まれなくなるわけではありません(4 MiB までは全部読まれる)
  • MEMORY.md の200行・25KBは「上限」です。ドキュメントによると、それより後ろは起動時に読まれません

MEMORY.md「201行目に、一番大事なことを書いておきました」

見習いさん「200行目で閉じました」

……申し送り帳の、最後のページが破れている。

CLAUDE.md は長くても全部読まれて、MEMORY.md は切られる。逆じゃないの?

役割が違うんです。 MEMORY.md は「目次」(1行1メモの索引)で、詳しい中身は別のファイルに分けて、必要なときに読む作りです。 目次が200行を超えたら、目次の書き方を見直すタイミングです。

ここから先は上級者向け。読み飛ばしてもOKです。

上級:コンテキスト圧縮のあと、何が残って何が消えるか

長い会話で「さっきまで守っていたのに」となるのは、たいてい圧縮のあとです。ルートの CLAUDE.md や auto memory は読み直され、会話の途中で読んだものは要約に溶ける、が基本です。

コンテキストウィンドウのドキュメントの表から、CLAUDE.md まわりを抜き出すとこうなります。

種類圧縮のあと
プロジェクトのルートの CLAUDE.md と、paths なしのルールディスクから読み直して入れ直す
auto memoryディスクから読み直して入れ直す
paths 付きのルール一致するファイルを Claude が読んだときに、また読み込まれる
サブフォルダの CLAUDE.mdそのサブフォルダのファイルを Claude が読んだときに、また読み込まれる
以前にフックが足した内容会話と一緒に要約される
compact に一致する SessionStart フック実行して、その出力を圧縮後のコンテキストに足す

(コンテキストウィンドウのドキュメントの表から、おうどんが一部を日本語にまとめたものです)

ドキュメントの説明では、paths 付きのルールとサブフォルダの CLAUDE.md は、きっかけのファイルを読んだときに会話の履歴の中に入るので、圧縮で要約されてしまいます。 圧縮をまたいで残したいルールは、paths を外すか、ルートの CLAUDE.md に移すように、と書かれています。

見習いさん「2階の申し送り帳は、2階に上がったときに読んだので、メモ帳にはさんでおきました」

圧縮「メモ帳、1ページに要約しておきました」

見習いさん「……2階のルール、なんでしたっけ」

……もう一度2階に上がれば思い出す。上がるまでは忘れている。

もう1つ大事なのが、会話で伝えただけの指示です。 memory のドキュメントでも、圧縮のあとで消えた指示は「会話でだけ伝えた」「まだ読み直されていないサブフォルダの CLAUDE.md にある」「最近一致していない paths 付きのルールにある」のどれか、とされています。 残したいなら CLAUDE.md へ、です。

口で言ったことは、要約されたら消える。

人間の会議と同じです。議事録(要約)に載らなかった発言は、次の週には誰も覚えていません。

上級:CLAUDE.md と auto memory は「書く人」が違う

似た名前の仕組みがもう1つあります。auto memory は、Claude が自分で書くメモです。memory のドキュメントの比較表をまとめると、こうなります。

CLAUDE.mdauto memory
書く人自分(人間)Claude
中身指示・ルール学んだこと・パターン
範囲プロジェクト・ユーザー・組織リポジトリごと(worktree の間で共有)
読み込み毎回毎回(MEMORY.md の先頭200行か25KB)

(memory のドキュメントの表を、おうどんが日本語にまとめたものです)

auto memory について、ドキュメントに書かれている主な点です。

  • 既定でオン。/memory の切り替えか、設定の autoMemoryEnabled、環境変数 CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 でオフにできる
  • 保存先は ~/.claude/projects/<project>/memory/。<project> は Git リポジトリから決まるので、同じリポジトリのサブフォルダや worktree は1つのフォルダを共有する
  • MEMORY.md は索引。1つ1つのメモ(トピックファイル)は起動時には読まれず、必要なときに Claude が読む
  • 手元のパソコンだけに保存され、ほかのマシンやクラウド環境とは共有されない
  • メインの会話の auto memory は、サブエージェント(別のコンテキストで動く手伝いの AI)には読み込まれない。フォークは例外

「Claude に覚えておいてと言ったのに、CLAUDE.md に書かれていない」というのは、これが理由のことがあります。 ドキュメントによると、「覚えておいて」と頼むと auto memory に保存され、CLAUDE.md に書かせたいなら「CLAUDE.md に追加して」とはっきり頼むか、/memory から自分で編集します。

店長「これ覚えておいて」

見習いさん「はい! 自分のメモ帳に書きました!」

店長「店の申し送り帳に書いてほしかったんだけど」

……同じ「覚えておいて」でも、書く帳面が違う。

auto memory があれば、CLAUDE.md はいらないのでは?

見習いさんのメモ帳は、見習いさんの判断で書かれます。 ドキュメントでも、毎回何かを保存するわけではなく、将来役に立つかで決めると書かれています。 店のルールは、店長が申し送り帳に書く。ここは分けておくのが安心です。

上級:InstructionsLoaded フックで「いつ読まれたか」を記録する

/context は「今どうなっているか」を見る道具です。 「いつ、なぜ読まれたか」の記録がほしいときは、InstructionsLoaded フックが使えます。

フックのドキュメントによると、

  • CLAUDE.md か .claude/rules/*.md がコンテキストに読み込まれたときに動く。起動時と、サブフォルダや paths 付きのルールがあとから読まれたとき
  • 止めたり判断を変えたりはできない。記録(観測)のためのフック
  • matcher(どのときに動かすか)は load_reason に対して照合される
  • 受け取る主な項目:file_path(読まれたファイル)、memory_type(User・Project・Local・Managed)、load_reason(session_start・nested_traversal・path_glob_match・include・compact)、trigger_file_path(include と nested_traversal のときのきっかけ)

受け取った JSON を1行ずつ記録する、Python のログ係です。

log_instructions.py:

"""InstructionsLoaded フックから呼ぶ想定のログ係。標準入力の JSON を1行ずつ記録する"""
import json
import sys
from datetime import datetime
from pathlib import Path

LOG = Path.home() / ".claude" / "instruction-log.txt"
if len(sys.argv) > 1:
    LOG = Path(sys.argv[1])

data = json.load(sys.stdin)
line = "{time} {reason:<16} {type:<8} {path}".format(
    time=datetime.now().strftime("%H:%M:%S"),
    reason=data.get("load_reason", "?"),
    type=data.get("memory_type", "?"),
    path=data.get("file_path", "?"),
)
if data.get("trigger_file_path"):
    line += f"  (きっかけ: {data['trigger_file_path']})"
LOG.parent.mkdir(parents=True, exist_ok=True)
with open(LOG, "a", encoding="utf-8") as f:
    f.write(line + "\n")

Claude Code からは呼ばず、ドキュメントの項目名に合わせたサンプルの JSON を4つ、標準入力に渡して動かしました。

{"hook_event_name": "InstructionsLoaded", "file_path": "C:/work/app/CLAUDE.md", "memory_type": "Project", "load_reason": "session_start"}
{"hook_event_name": "InstructionsLoaded", "file_path": "C:/work/app/docs/git-instructions.md", "memory_type": "Project", "load_reason": "include", "trigger_file_path": "C:/work/app/CLAUDE.md"}
{"hook_event_name": "InstructionsLoaded", "file_path": "C:/work/app/src/api/CLAUDE.md", "memory_type": "Project", "load_reason": "nested_traversal", "trigger_file_path": "C:/work/app/src/api"}
{"hook_event_name": "InstructionsLoaded", "file_path": "C:/work/app/CLAUDE.md", "memory_type": "Project", "load_reason": "compact"}

(1行ずつ別々に渡しています。本物のフックの入力には、ほかに共通の項目も付きます)

記録されたログ:

08:12:20 session_start    Project  C:/work/app/CLAUDE.md
08:12:20 include          Project  C:/work/app/docs/git-instructions.md  (きっかけ: C:/work/app/CLAUDE.md)
08:12:21 nested_traversal Project  C:/work/app/src/api/CLAUDE.md  (きっかけ: C:/work/app/src/api)
08:12:21 compact          Project  C:/work/app/CLAUDE.md

(今回の検証環境の Windows 10・Python 3.13.1 で、Python の subprocess から標準入力に渡して実行した結果です)

フックとして登録する設定の例です。~/.claude/settings.json の hooks に書きます。

{
  "hooks": {
    "InstructionsLoaded": [
      {
        "matcher": "path_glob_match|nested_traversal|compact",
        "hooks": [
          {
            "type": "command",
            "command": "python C:/Users/you/.claude/hooks/log_instructions.py"
          }
        ]
      }
    ]
  }
}

(未実行の設定例です。Claude Code に読み込ませての動作は、今回の検証では確かめていません。パスは説明用です)

matcher を「あとから読まれたとき」と「圧縮で読み直されたとき」に絞っています。 起動時の分は /context で見られるので、ログは「途中で何が増えたか」に集中させる作戦です。

ログ係「10時3分、2階の申し送り帳を読みました。きっかけは2階の厨房です」

店長「……見習いさんより、ログ係のほうが記憶力がいい」

……ログ係はファイルに書いているので、忘れようがない。

ログ係に「守らなかったら止めて」って頼めない?

頼めません。ドキュメントのとおり、このフックは止められない、記録専門の係です。 止める係は、次の章です。

上級:「必ず」守らせたいなら、申し送りではなく仕組み

最後は設計の話です。CLAUDE.md で「お願い」、設定とフックで「強制」と、役割を分けます。

memory のドキュメントでは、組織向けの説明の中で、こんな使い分けの表が出てきます。

やりたいこと書く場所
特定のツール・コマンド・パスを止める設定の permissions.deny
サンドボックスで隔離する設定の sandbox.enabled
コードの書き方・品質の方針CLAUDE.md
Claude への振る舞いの指示CLAUDE.md

(memory のドキュメントの表から一部を、おうどんが日本語にまとめたものです)

そして、設定のルールは Claude の判断に関係なくクライアント(Claude Code)が強制し、CLAUDE.md の指示は振る舞いを形づくるが強制の層ではない、と書かれています。

ほかに、ドキュメントで案内されている手段です。

  • 決まったタイミングで必ず動かしたい → フック(PreToolUse なら、ツールを使う前に止められる)
  • システムプロンプトのレベルで指示したい → 起動時の --append-system-prompt(スクリプトや自動化向け)
  • CLAUDE.md のコミットのルールが、Claude Code の組み込みのコミットの指示とぶつかる → includeGitInstructions で組み込みの指示を切り、attribution で署名の文を設定する

見習いさん「申し送り帳に『金庫は開けない』と書いてあったので、開けませんでした」

店長「えらい」

見習いさん「でも鍵はかかってなかったです」

……お願いは守ってくれた。でも、鍵は別の話。

「金庫を開けない」は申し送り帳に書くだけでなく、鍵(権限設定)もかけておく。 逆に「インデントは2つ」のような好みは、申し送り帳で十分です。

全部の約束をフックにすれば、申し送り帳いらないのでは?

「変数名は分かりやすく」をフックで判定するスクリプト、書けますか。 おうどんは書ける気がしません。 仕組みにできるものは仕組みに、できないものは申し送り帳に。

チェックリスト

  • /context の Memory files に、読ませたい CLAUDE.md が出ている
  • 最初から守ってほしいルールは、起動するフォルダ(かその上)の CLAUDE.md に書いた
  • サブフォルダの CLAUDE.md は「そこのファイルを読んだとき」に読まれると理解している
  • 複数の CLAUDE.md・ルールで、矛盾する指示を書いていない
  • 指示は確かめられるくらい具体的に書いた
  • 1ファイル200行未満を目安にし、長いものは paths 付きのルールに分けた
  • @ インポートの先が存在し、クォートで囲まず、スペースは \ にした
  • 外部インポートの承認ダイアログで「いいえ」を押していない
  • 圧縮をまたいで残したい指示は、会話ではなくルートの CLAUDE.md に書いた
  • 「必ず」守らせたいことは、権限設定かフックにした

10個。申し送り帳の申し送り帳。

申し送り帳を正しく読ませるための申し送り帳。 でも、これを1回やれば、毎朝の「はじめまして」が少しだけ怖くなくなります。

このチェックリストも CLAUDE.md に書いておけば……

見習いさん「申し送り帳の書き方が書いてある申し送り帳を読みました!」

……入れ子になって、店長が混乱する。 チェックリストは、人間が見る用です。

まとめ

冒頭では、見習いさんが毎朝「はじめまして!」と出勤していました。

これは直りません。 Claude Code のセッションは、毎回まっさらから始まる作りだからです。 直せるのは、申し送り帳の置き場所と書き方です。

  • CLAUDE.md は命令ではなく、読んでもらう文章。具体的・短く・矛盾なく
  • 起動したフォルダとその上は起動時、サブフォルダはそこのファイルを読んだとき
  • 複数のファイルは上書きではなく、つなげて読まれる
  • まず /context で読まれているかを確かめる
  • 圧縮のあと、ルートの CLAUDE.md は読み直される。会話だけの指示は要約に溶ける
  • 「必ず」は、申し送りではなく権限設定とフックで

店長「よし、見習いさんに記憶力の特訓を……」

……特訓の内容も、明日の朝には「はじめまして」です。

見習いさんの記憶力を鍛えるのではなく、店長が申し送り帳を正しい机に、正しい書き方で置いておく。それが、毎朝まっさらな相棒と働くコツです。

見習いさん「はじめまして! 申し送り帳、読みました。今日もインデントは2つ、コミットの前に npm test、ですね」

店長「……はじめましてなのに、昨日の約束を知ってる」

……それでいいんです。覚えていなくても、読めば分かる。

まずは今日、Claude Code で /context を1回打ってみてください。 机の上の申し送り帳が、ちゃんと並んでいるかどうか。 並んでいたら、今日の見習いさんとも、おいしいかけうどんが作れますように🍜

参考資料

  • Claude Code Docs:How Claude remembers your project — セッションはまっさらから始まること、CLAUDE.md の置き場所と読み込み順、上のフォルダは起動時・サブフォルダはあとから読まれること、つなげて読まれること、@ インポートの決まり(相対パスの基準・4段・スペース・クォート・コードスパン)、外部インポートの承認、--add-dir と環境変数、.claude/rules/ と paths、200行の目安と 4 MiB、AGENTS.md との関係、auto memory(保存先・200行と25KB・オンオフ)、/memory と /context、守られないときの切り分け、圧縮後に消えた指示、設定と CLAUDE.md の使い分け
  • Claude Code Docs:Explore the context window — 圧縮のあとに残るもの・読み直されるもの・要約されるものの表、paths 付きのルールとサブフォルダの CLAUDE.md が要約されること
  • Claude Code Docs:Hooks reference — InstructionsLoaded フックが動くタイミング、止められないこと、matcher と load_reason、入力の項目

確認日:2026-09-30。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

おうどん|癒しと創作をたのしむ雑食クリエイター
写経アプリやクレイセラピー、優しい和風デザインがすき。
ZARDと刀剣と文字に癒されて、今はアプリ作ってます。

コメント

コメントする

目次