こんにちは、おうどんです🍜
朝。 うどん屋さんの勝手口が開く。
見習いさん「はじめまして! 今日からお世話になります!」
店長(おうどん)「……昨日もいたよね?」
見習いさん「はじめまして!」
……毎朝、はじめまして。
(場面は説明用の架空のものです)
これ、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.md | auto 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。

コメント