Claude CodeのworktreeでCLAUDE.mdが二重に読み込まれていた ─ claudeMdExcludesで除外し、効果を測ろうとした話

当ページのリンクには広告が含まれています。
この記事の結論
Claude Codeで作業用のworktreeに移ると、メインと同じ内容の CLAUDE.md がもう一度読み込まれていました。セッションログから回数を数え、claudeMdExcludes でworktree直下の指示ファイルだけを除外しています。ただ、導入後の「0回」は、まだ設定が効いた証明にはなっていないと考えています。

お疲れ様です!IT業界で働くアライグマです!

個人開発している開発関連ニュースのキュレーションサービス「DevPick」では、Claude Codeでの作業をgit worktreeで分けて進めています。便利なので当たり前のように使っていたのですが、実はその裏で、同じ規約の文書がAIの作業領域(コンテキスト)に2回載っていました。今回は、その重複をログから数えて設定で外し、効果を確かめようとしたら思ったより簡単には確かめられなかった、というところまでをお話しします。

目次

worktreeに移ると、同じ規約がもう一度読み込まれていた

DevPickでは、Issueに着手するときのClaude Codeのスキル(issue-work)が、作業ごとに専用のworktreeを作り、作業ディレクトリをそこへ移します。作る場所は、リポジトリの中の .claude/worktrees/<名前>/ です。レビュー待ちの間に別の修正を並行して進められるので重宝している運用で、Claude Codeで高速開発しながら品質を担保するTipsでもworktreeの使い方に触れています。

worktreeはリポジトリをもう1つ取り出したものなので、中にはリポジトリ直下と同じ CLAUDE.md がそのまま入っています。DevPickの CLAUDE.md は22,369バイトあり、Codex向けの AGENTS.md(CLAUDE.md へのシンボリックリンク)と、Gemini向けの GEMINI.md も同じ内容です。

ここで効いてくるのが、Claude Codeが CLAUDE.md を読み込むタイミングです。公式ドキュメントによると、作業ディレクトリより上の階層にある CLAUDE.md は起動時に読み込まれます。一方で、作業ディレクトリより下の階層にある CLAUDE.md は、Claudeがそのディレクトリのファイルを読んだときに追加で読み込まれます。この追加の読み込みは、セッションログ上では「nested memory」として記録されています。

DevPickの構成に当てはめると、次のようになります。

devpick/
├── CLAUDE.md                        ← 起動時に読み込まれる
└── .claude/worktrees/
    └── issue-xxx/                   ← 作業用のworktree
        ├── CLAUDE.md                ← 中のファイルを読むと、追加で読み込まれる
        └── AGENTS.md                (CLAUDE.md へのシンボリックリンク)

worktreeはメインの作業ディレクトリの「下」にあるので、worktree内のファイルを読むと、その直下の CLAUDE.md が下の階層の指示ファイルとして扱われます。中身はメインの CLAUDE.md とほぼ同じなので、同じ規約が文脈に2回載ることになります。

しかも、一度読み込まれた内容は、そのセッションの残りのターンでも毎回文脈に含まれます。Issueを書いた時点の見積もりでは、1ターンあたり約7千トークンの上乗せで、worktreeに移ってから100ターン動けば約70万トークンになる計算でした。なお、この少し前に CLAUDE.md 自体を軽くする対応をしていました。ただそれは文書の中身を削る対応で、同じ文書が2回読み込まれる経路は手つかずのままでした。

IT女子 アラ美
せっかく文書を削って軽くしたのに、それが2回載ってたら意味ないじゃない。

ITアライグマ
そうなんです。中身を削ることと、読み込まれる回数を減らすことは別の対策でした。

セッションログから数えた:14日で21回・41万文字

「2回載っているはず」という推測のまま設定を変えると、変えた後に本当に減ったのか確かめられません。そこで、除外の前に、どれだけ読み込まれていたかを数えることにしました。

Claude Codeは、セッションごとのやり取りをJSON Lines形式のログとして手元に残しています。追加で読み込まれた CLAUDE.md も、このログに「添付(attachment)」の一種として、読み込んだファイルのパスと本文つきで記録されます。DevPickには、このログからエージェントの利用状況を集計するスクリプトが以前からあったので、そこに --nested-memory というオプションを足しました。数える対象は、nested memoryとして記録された添付のうち、パスが「worktree直下の CLAUDE.md か AGENTS.md」に一致するものだけです。

# worktree直下の指示ファイルだけに一致させる
WORKTREE_INSTRUCTION_PATTERN = re.compile(
    r"/\.claude/worktrees/[^/]+/(?:CLAUDE|AGENTS)\.md$"
)

# ログの1行ごとに、nested memoryの添付で、パスが一致するものだけを数える
if kind == "attachment":
    attachment = entry.get("attachment") or {}
    if attachment.get("type") == "nested_memory" and WORKTREE_INSTRUCTION_PATTERN.search(
        str(attachment.get("path") or "")
    ):
        worktree_memory_loads += 1
        # 本文の文字数も合計する(実装から一部を抜粋)

数え方を絞ったのには理由があります。メインの CLAUDE.md は起動時の正規の読み込みなので、数えてはいけません。worktreeの中のサブディレクトリにある CLAUDE.md も、後で説明するとおり重複ではないので対象外です。テストでは、これらの「数えてはいけないもの」をわざと混ぜたログを用意し、worktree直下の2ファイルだけが数えられることを確かめました。読み込みが1回もないときに、1回あたりの平均を出そうとして0で割ってしまわないことも、別のテストで確認しています。

実行すると、直近14日分のClaude Codeセッションで、次の結果になりました。

対象 読み込みがあったセッション 読み込み回数 合計文字数(1回平均)
直近14日のClaude Codeセッション 103件中19件 21回 412,902文字(19,662文字)

1回あたり約2万文字は、ほぼ CLAUDE.md 1本分です。14日の間に19のセッションで、規約が丸ごともう1本載っていたことが数字で確認できました。AIに渡す情報の量を絞る話は、AIにテストさせたら、成功ログだけでコンテキストが埋まっていた話でも書きました。あのときはテストの出力でしたが、今回は指示ファイルそのものが重複していたことになります。

IT女子 アラ美
41万文字って、ちょっとした本が丸ごと入る量よね。気づかないものなの?

ITアライグマ
ログを数えるまでは、どれだけ重なっているのか分かっていませんでした。

除外はworktree直下だけに絞った

対策の候補は2つありました。1つは、Claude Codeの設定で、worktree内の CLAUDE.md を読み込み対象から外す方法です。もう1つは、worktreeを作る場所をリポジトリの外へ移す方法です。後者は根本的ですが、「編集はworktreeで行うこと」「worktreeで依存パッケージをインストールしないこと」を強制しているフックが、worktreeの場所を前提にしています。場所を変えるなら、それらのフックとテスト、スキルの手順も一緒に直す必要がありました。

今回は前者を選びました。Claude Codeには claudeMdExcludes という設定があり、読み込みたくない CLAUDE.md をパスやglobパターンで指定できます。公式ドキュメントでは、大きなモノレポで他チームの CLAUDE.md が読み込まれてしまう場合に使うものとして紹介されています。パターンは絶対パスに対して照合され、ユーザー・プロジェクトなどの設定のどの階層にも書けて、配列は階層をまたいで結合されます。

前の章の計測用オプションと同じ変更の中で、プロジェクトの .claude/settings.json に次の2行を足しました。

{
  "claudeMdExcludes": [
    "**/.claude/worktrees/*/CLAUDE.md",
    "**/.claude/worktrees/*/AGENTS.md"
  ]
}

ポイントは、* を1段だけにして、worktreeの直下にある指示ファイルだけを外していることです。worktreeの中のサブディレクトリに CLAUDE.md を置いた場合、それはメイン側からは読み込まれない、そのディレクトリ固有の規約になります。それまで外してしまうと、本来必要な指示まで消えてしまいます。重複しているのはworktree直下の1本だけなので、そこだけを狙いました。

外したあとも、規約自体が読み込まれなくなるわけではありません。worktreeはメインの作業ディレクトリの下にあるので、メインの CLAUDE.md は上の階層のファイルとして、これまでどおり起動時に読み込まれます。読み込みが2回から1回になるだけです。

もう1つ確認したのが、他のAIエージェントへの影響です。DevPickでは同じ規約をCodexやGemini(AGY)にも読ませています。Claude CodeにGeminiを導入したときのスキルの二重管理の話で書いたとおり、複数のエージェントで規約を共有する構成です。claudeMdExcludes はClaude Code専用の設定なので、Codexが AGENTS.md を、Geminiが GEMINI.md を読む挙動には影響しません。Codexはworktreeで動くとき、そのworktreeの AGENTS.md を1回読むだけで、もともと重複していませんでした。

IT女子 アラ美
全部まとめて外しちゃえば楽なのに、わざわざ1段だけにしたのね。

ITアライグマ
はい。下の階層の固有ルールまで消すと、別の事故になるので避けました。

本体側で既に止まっていたかもしれない。それでも設定で固定した

ログを詳しく見ていくと、21回の読み込みには偏りがありました。21回とも、人が操作する対話セッションでのものだったのです。そして最後に読み込まれたのは、10月1日のClaude Code 2.1.285です。その後の2.1.286〜2.1.288では、対話セッションでworktree内のファイルを読んだ場面が6回ありましたが、どれでも読み込まれていませんでした。

つまり、設定を足す前の時点で、Claude Code本体がすでにworktree直下の CLAUDE.md を読まなくなっていた可能性があります。

それでも設定は入れることにしました。理由は、その挙動がドキュメントで約束されていないからです。公式ドキュメントで「worktree直下の CLAUDE.md を読み込まない」と明記されているのは、既定の場所(.claude/worktrees/ の下)のworktreeでsubagentが動く場合だけです。通常のセッションがworktreeに移った場合について、同じことは書かれていません。

明記されていない挙動は、次のバージョンで元に戻っても文句は言えません。たまたま止まっている状態に頼るより、明文化された設定である claudeMdExcludes で除外を固定しておけば、本体の挙動が変わっても二重読み込みは戻りません。仮に本体側で本当に直っていたとしても、設定が2行増えるだけで害はないと判断しました。

もう1つ、ログを見て分かったことがあります。ヘッドレス実行(claude -p)のセッションは、設定を変える前からnested memoryをログに記録していませんでした。ヘッドレス実行のセッションについては、読み込まれたかどうかをログで判定できないということです。そのため、前後比較は対話セッションのログだけで行うことにしました。

IT女子 アラ美
もう直ってるかもしれないのに設定するって、念のための保険みたいなものね。

ITアライグマ
はい。ドキュメントにない挙動に頼るより、自分で固定しておくほうが安心です。

導入後の「0回」は、まだ効果の証明になっていない

設定を入れた翌日の10月4日に、導入前と同じスクリプトで読み込み回数を数え直しました。比べ方を揃えるため、オプションも導入前と同じものを使い、集計期間だけを変えています。

python3 scripts/agent-usage-report.py --agent claude --days 14 --nested-memory
# 対象 110セッション中 19セッション / 21回 / 合計 412,902文字(1回平均 19,662文字)

python3 scripts/agent-usage-report.py --agent claude --days 1 --nested-memory
# 対象 27セッション中 0セッション / 0回 / 合計 0文字

14日分の集計では、対象のセッションが103件から110件に増えた一方で、読み込みは21回のまま増えていません。直近1日分に絞ると0回です。数字だけを見れば「除外が効いた」と言いたくなります。

ただ、この0回には中身がありませんでした。導入後のDevPickの対話セッション5件のログを個別に見ると、worktree内のファイルを読む操作(Read)が1回もなかったのです。worktreeに触れていたのは、Bashでのコマンド実行33回と、ファイルの書き込み3回だけでした。nested memoryの読み込みは、そのディレクトリのファイルを読んだときに起きるものです。読む操作がなければ、設定があってもなくても0回になります。

さらに前の章で書いたとおり、2.1.286以降は設定を入れる前から読み込まれていませんでした。導入後のセッションはすべて2.1.288です。仮にworktree内のファイルを読んでいたとしても、0回の理由が設定なのか本体側の変化なのかは区別できません。

つまり、今の時点で言えるのは「導入後に二重読み込みは起きていない」ということまでです。「設定のおかげで起きていない」とはまだ言えません。今後、worktree内のファイルを読むセッションが溜まった時点で、同じコマンドをもう一度実行する予定です。それでも0回なら、少なくとも二重読み込みが戻っていないことは確認できます。0回でなければ、ログに残ったパスの書き方(シンボリックリンク経由の表記など)が除外パターンと一致していないということなので、ログの path に合わせてパターンを直します。

IT女子 アラ美
0回って出たら普通は喜んで終わりよね。中身まで見たのはえらいと思う。

ITアライグマ
ありがとうございます。読む操作がなければ0回になるので、まだ喜べませんでした。

まとめ

今回の対応を振り返ると、次のように整理できます。

  • worktreeをリポジトリの中に置くと、指示ファイルが下の階層のものとして読み込まれる:Claude Codeは下の階層の CLAUDE.md を、そこにあるファイルを読んだときに追加で読み込みます。worktree直下の CLAUDE.md はメインと同じ内容なので、同じ規約が2回載っていました。
  • 推測で直す前に、ログで数える:セッションログのnested memoryの記録から、14日で21回・412,902文字の読み込みを数えました。数え方を先に作っておいたので、導入後も同じ方法で比べられます。
  • 除外は重複している範囲だけに絞る:claudeMdExcludes で外したのはworktree直下の2ファイルだけです。メインの CLAUDE.md は引き続き読み込まれ、サブディレクトリ固有の規約も残ります。
  • ドキュメントにない挙動には頼らない:本体側で既に止まっていた可能性はありますが、明記されているのはsubagentの場合だけでした。明文化された設定で固定しておけば、本体の挙動が変わっても戻りません。
  • 0回の中身を確かめる:導入後は0回でしたが、worktree内のファイルを読む操作自体がありませんでした。効いたかどうかは、読む操作を含むセッションが溜まってから判断します。

AIに渡す文脈を減らす対策というと、指示ファイルを短く書き直すことをまず考えがちです。今回は、短くした文書が2回読み込まれていたという、別の経路の無駄でした。そして、減らしたつもりの効果を測るときも、「0回」という数字だけで終わらせず、0回になる条件がそろっていたかまで見る必要があると感じています。

IT女子 アラ美
短くするのと、何回読まれてるかを数えるのは別ってことね。覚えておくわ。

ITアライグマ
はい。効果の確認も、数字が出る前提がそろっているかまで見るようにしています。

運営サービス・姉妹メディア

この記事をシェアする
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ITアライグマのアバター ITアライグマ ITエンジニア / PM

都内で働くPM兼Webエンジニア(既婚・子持ち)です。
AIで作業時間を削って実務をラクにしつつ、市場価値を高めて「高年収・自由な働き方」を手に入れるキャリア戦略を発信しています。

目次