はじめに
自分は1年半ほど、業務メモや調査ノートをMarkdownで1つのリポジトリに溜めて、Claude CodeとCodexの両方に読ませています。ある日「タチバナ社(仮名)の案件で使ったSQLはどこ?」と聞いたら「見当たりません」と返ってきました。ファイルはありました。英字で Tachibana と書いてあっただけです。
Claude CodeもCodexも、リポジトリの索引を使わず、質問のたびにgrepの仲間(ugrep・ripgrep)でファイルを探しています。Claude Codeは公式がそう明言していて、Codexは動きを見る限り同じです。だから「タチバナ」で検索した文書は「Tachibana」では見つからず、自分のリポジトリで数えたら片方の表記だけで149ファイルを取りこぼしていました。
この記事では彼らがファイルを検索する仕組みと、
それに対して我々ユーザーはどうコンテキストを当てたら良いのかを考えます。
本文中の会社名・製品名はすべて架空のものに置き換えています。件数は2026年9月3日に自分のリポジトリで実測した値です。Claude Codeの検索ツールの構成は執筆時点(2026年9月25日、v2.1.281)の公式リファレンスに基づいています。バージョンで変わる部分なので、読む時期によっては違っているかもしれません。
参考文献
Claude Code側の仕様はこの公式リファレンスが一次情報です。
Codex側は、OpenAIのプロンプトガイドと、GitHubで公開されているシステムプロンプトの原文を読みました。
「索引を作らない」という設計判断は、Claude Codeの開発者がHacker Newsで述べたコメントと、Anthropicのエンジニアリングブログが出典です。
検索コマンドそのものの挙動は、ripgrepとugrepの公式リポジトリを見ています。
検証環境
- macOS(Apple Silicon)
- Claude Code 2.1.281(デスクトップアプリ)、Codex CLI 0.144.1。両方を同じMarkdownリポジトリに対して使用
- Claude Codeのシェル内で
grep --versionなどを打って確認した実体は、ugrep 7.8.4、bfs 4.1.1、ripgrep 14.1.1 - リポジトリの規模は、ripgrepの既定フィルタで約7,500ファイル。うちMarkdownが約2,600ファイル
- 実測日は2026年9月3日
Claude CodeもCodexも索引を引いていない
「AIエージェントはリポジトリを読み込んで理解している」と思っていませんか?
実際は違います。どちらも事前索引を引く様子はなく、質問のたびにファイルシステムを探索します。質問を受けてから、関連しそうな語を考え、ファイル名で当たりを付け、本文を正規表現で検索し、候補を読み、足りなければ検索語を変えてやり直す。この5段のループが検索処理そのものです。
ループの1段目と5段目はモデルの判断で、2〜4段目が道具です。ここに索引が登場しないので、データは常に最新ですが、代わりにトークンと時間で払っています。
Claude Codeの開発者であるBoris Cherny氏は、Hacker Newsでこう書いています。
Claude Code doesn't use RAG currently. In our testing we found that agentic search out-performed RAG for the kinds of things people use Code for.
Anthropicのエンジニアリングブログも同じ設計思想を「just in time」と呼んでいて、事前に全データを処理する代わりに、ファイルパスのような軽い識別子だけを持ち、必要なときにツールで読み込むと説明しています。正確には、CLAUDE.md のような指示ファイルを先に読み込んだうえで残りを実行時に探す「hybrid」だと書かれています。
Codexについては、公開されているシステムプロンプトが示すのは「検索には rg を使え」という方針までで、内部に索引が無いことの証明にはなりません。ただ、実際に使っていて索引を作る様子はなく、探索の動きはClaude Codeと同じでした。
道具の違い
同じループを回しますが、道具の粒度が違います。ここは公式リファレンスを読み直して、自分の理解が1つ古かったところです。
Claude Codeには Glob(ファイル名で探す)・Grep(本文を探す)・Read(読む)という専用ツールがあり、Grep はripgrepの上に作られています。ここまでは知っていました。知らなかったのは、執筆時点の公式リファレンスにこう書いてあることです。
On macOS, Linux, and WSL, Claude Code leaves Glob and Grep out of the default tool set, and Claude searches with
findandgrepthrough the Bash tool instead. In Claude's shell those two commands run embedded versions ofbfsandugrep.
つまりmacOSやLinuxでは、既定では Glob も Grep も無く、ClaudeはBashで find と grep を打ちます。しかもClaudeのシェルの中では、その grep は同梱のugrep、find は同梱のbfsに差し替わっています。--allowedTools で Grep を名指しした場合などに、ripgrepベースの専用ツールが戻ってきます。
本当かどうか、Claude Codeに自分のシェルで確かめさせました。
$ type grep find rg
grep is a shell function from /Users/.../.claude/shell-snapshots/snapshot-zsh-....sh
find is a shell function from /Users/.../.claude/shell-snapshots/snapshot-zsh-....sh
rg is a shell function from /Users/.../.claude/shell-snapshots/snapshot-zsh-....sh
$ grep --version | head -1
ugrep 7.8.4 aarch64-apple-macosx +neon/AArch64; -P:pcre2jit; ...
$ find --version | head -1
bfs 4.1.1
$ rg --version | head -1
ripgrep 14.1.1 (rev f08e57bec0)
$ /usr/bin/grep --version
grep (BSD grep, GNU compatible) 2.6.0-FreeBSD
grep も find も rg も、Claude Codeが起動時に作るシェルスナップショットの中でシェル関数に置き換わっていて、システムのBSD grepではなく同梱のugrepが動いていました。grep の関数を読むと、ugrepを -G --ignore-files --hidden -I --exclude-dir=.git などを付けて呼んでいます。.gitignore を尊重し、バイナリを飛ばし、.git の中を見ない、というripgrepに近い既定に寄せてあるわけです。rg の関数はClaude Code本体の実行ファイルを rg という名前で呼び出す作りで、ripgrepが本体に埋め込まれています。この環境ではPATHに rg を入れていなかったので、内蔵ripgrepへ流す関数が定義されていました。
Codex側は、システムプロンプトにこの一文が入っています。
When searching for text or files, prefer using
rgorrg --filesrespectively becausergis much faster than alternatives likegrep. (If thergcommand is not found, then use alternatives.)
さらにプロンプトガイドには「Never read files one-by-one unless logically unavoidable」とあり、並列で読めと明示しています。Codexは専用の検索ツールを持たず、シェルで rg を打ちます。rg が無ければ grep などの代替手段を使う、という指示です。
整理するとこうなります。
| 観点 | Claude Code(macOS / Linux / WSL の既定) | Claude Code(Grep を有効化した場合) |
Codex |
|---|---|---|---|
| ファイル名で探す | Bashで find(実体はbfs) |
Glob |
rg --files |
| 本文を探す | Bashで grep(実体はugrep) |
Grep(実体はripgrep) |
rg(無ければ grep などの代替) |
| 中身を読む | Read |
Read |
cat や sed -n
|
.gitignore の扱い |
grep はシェル関数がugrepに --ignore-files を付けて呼ぶので、検索対象のツリー内の .gitignore を尊重する。find(bfs)は自動では尊重しない |
Glob は無視しない(CLAUDE_CODE_GLOB_NO_IGNORE=false で尊重)、Grep は尊重 |
rg は既定で尊重 |
| 事前指示ファイル | CLAUDE.md |
CLAUDE.md |
AGENTS.md |
Glob は結果が更新時刻順で100件までに切られ、上限に当たるとClaudeに打ち切りフラグが見えるので、パターンを絞り直す動きになります。Grep は出力モードが3つ(files_with_matches、content、count)あり、glob や type で範囲を絞れます。
道具の名前はバラバラですが、共通しているのは「正規表現で文字列を探す」ことです。ugrepもripgrepもGNU grepの仲間で、どれも -i で吸収するのは大文字小文字だけです。表記ゆれの取りこぼしは、どのgrepでも同じように起きます。ただし正規表現の方言は違っていて、それが後で1つ罠になります。
なぜ索引を作らないのか
RAGの記事を書いたことがある人ほど「索引を作ったほうが速いのでは」と感じるはずです。自分も以前にRAGとMCPサーバーの違いという記事で、事前にインデックス化した文書を検索する方式を紹介しました。
コードやドキュメントの探索に限っては、索引を作らない側に合理性があります。
| 理由 | 何が起きるか |
|---|---|
| 鮮度 | 索引は作った瞬間から古くなる。コードは分単位で変わる |
| 構造の破壊 | チャンク化で関数が途中で切れ、importと使用箇所が離れる |
| 完全一致の弱さ | 識別子・型番・エラーコードはEmbeddingで意味が薄まる |
| 既存構造の放棄 | ディレクトリ・import・相対リンクという既にある構造を捨てることになる |
3つ目が重要です。HealthLake と HealthScape のような綴り違いは、ベクトル空間ではほぼ隣接します。しかも類似度の閾値を設けない単純なtop-k検索は「該当なし」を返せず、必ず上位k件を返します(閾値やメタデータフィルタを付ければ0件も返せますが、素朴なRAGではあまり付けません)。綴りを間違えた質問に対して、意味的に近い別物が正解と同じ顔で返ってくるわけです。
grepは0件を返します。0件は情報のある結果で、エージェントは「無い」を検知して検索語を変えられます。失敗が見えるか隠れるか、という違いです。
ここまでが「索引を作らない方式の強み」です。ここからが、その裏返しの弱点の話になります。
「タチバナ」で検索して「Tachibana」が0件になった
grepの -i が吸収するのは大文字小文字だけです。カタカナと英字の壁は越えません。ここから先の実行例はripgrepです。
まず最小構成で再現します。作業用の一時ディレクトリに3つのMarkdownを作って検索してみました(結果の並び順はripgrepでは保証されないので --sort path を付けています)。
cd "$(mktemp -d)"
mkdir notes
printf '# 会議メモ\n\nタチバナ社との定例。次回は来週。\n' > notes/meeting.md
printf '# 契約\n\nTachibana Co., Ltd. と締結済み。\n' > notes/contract.md
printf '# 案件一覧\n\n- タチバナ(Tachibana): 進行中\n' > notes/projects.md
$ rg --sort path -il タチバナ
notes/meeting.md
notes/projects.md
$ rg --sort path -il tachibana
notes/contract.md
notes/projects.md
$ rg --sort path -il "タチバナ|tachibana"
notes/contract.md
notes/meeting.md
notes/projects.md
Claude Codeのシェルで grep -ril タチバナ notes と打っても(実体はugrep)、単語1つの検索なら結果は同じでした。| や + を含むパターンでは同じになりません。この話は後でもう一度出てきます。
当たり前と言えば当たり前です。問題は、自分のリポジトリでこれがどのくらいの規模で起きているかでした。数えてみます。
rg -l "タチバナ" | sort > kata.txt
rg -il "tachibana" | sort > latin.txt
comm -12 kata.txt latin.txt | wc -l
comm -23 kata.txt latin.txt | wc -l
comm -13 kata.txt latin.txt | wc -l
| 検索語 | ヒットしたファイル数 |
|---|---|
タチバナ |
193 |
tachibana(-i で大小無視) |
94 |
| 両方が書かれている | 44 |
| カタカナのみ | 149 |
| ラテンのみ | 50 |
英字で探すと149ファイルを、カタカナで探すと50ファイルを取りこぼします。しかも英字側の内訳は tachibana が100回、Tachibana が80回、TachiBana が22回と、大小の揺れが3種類ありました。正式表記は1つのはずなのに、書いた時期や書いた人(自分とエージェントの両方)で割れていたんです。
エージェントが「見当たりません」と言ったとき、それは「無い」ではなく「片方の表記で探して0件だった」だと分かりました。
ローマ字化では解けない語がある
「じゃあカタカナと英字の両方で検索させればいい」と考えました。エージェントへの指示に「カタカナ語はローマ字表記も併せて検索すること」と書けば済む、と。
済みませんでした。
架空の例で言うと、社内では「モリノ」と呼んでいるサービスの正式ブランド表記が MORI+ だった、というケースです。自分のリポジトリで実際に数えると、この種の語は表記が5つに割れていました。
| 表記 | 出現回数 |
|---|---|
| ローマ字(小文字) | 264 |
ブランド表記(XXX+) |
215 |
ブランド表記(全角プラス XXX+) |
88 |
| ローマ字(大文字) | 50 |
| ローマ字(先頭大文字) | 4 |
カタカナからブランド表記は推測できません。「検索時は両表記で打て」という規則だけでは解けず、対訳の辞書そのものが要ります。
さらに罠が2つ重なります。+ は正規表現のメタ文字で、全角の + は半角とは別の文字列です。
printf '# アプリ\n\nMORI+ の配信設定メモ。\n' > notes/app.md
printf '# 表記ゆれ\n\nMORI+(全角プラス)で書かれた資料。\n' > notes/zenkaku.md
printf '# 無関係\n\nMORIII という文字列だけある。\n' > notes/noise.md
$ rg --sort path -l "MORI+"
notes/app.md
notes/noise.md
notes/zenkaku.md
$ rg --sort path -l "MORI\+"
notes/app.md
$ rg --sort path -l "MORI\+|MORI+"
notes/app.md
notes/zenkaku.md
MORI+ をそのまま渡すと「MORのあとにIが1回以上」と解釈され、無関係な MORIII まで拾います。エスケープすると今度は全角のファイルが落ちます。両方書いてやっと揃います。
正直、ここで「エージェントに検索を任せる」ことの解像度がガラッと変わりました。任せていたのは検索ではなく、正規表現を組む作業だったんです。
実際に起きた実害
検索漏れだけなら「もう一回聞けばいい」で済みます。厄介だったのはリンク切れです。
自分のリポジトリでは、業務のフォルダが 02_work/タチバナ/ というカタカナ名で存在します。ところが、これを 02_work/Tachibana/ と英字で指している参照が7箇所残っていました。エージェントが書いたものも、自分が書いたものもあります。
うち3箇所は、SQLの雛形を生成するスキル(Claude Codeに手順を教えるファイル)の中で「実例SQLはここにある」と案内している記述でした。参照先が存在しないので、そのスキルの「過去の実例を参考にする」機能は空振りしていました。数か月そのままで、誰も気づいていません。エージェントは「参照先が無い」とは言わず、実例なしで雛形を作って返していただけでした。
「見つからなかった」がエラーとして上がってこないのが、この方式の一番怖いところです。0件は情報のある結果だと先に書きましたが、それはエージェントが0件を「異常」と解釈した場合の話で、「無いなら無いで進める」と解釈されたら何も起きません。
辞書は「探すもの」ではなく「常に載っているもの」に置く
対策として、まず 類義語.md のような辞書ファイルを作ろうとしました。
これは鶏と卵になります。辞書ファイルを見つけるためには、失敗しているその検索そのものが必要だからです。「タチバナ」で探して0件のときに、類義語.md を開こうと思い立つ保証はどこにもありません。
なので辞書は、探さなくても常にコンテキストに載っているファイルに置きました。Claude Codeなら CLAUDE.md、Codexなら AGENTS.md です。自分は AGENTS.md を実体にして、CLAUDE.md からは @AGENTS.md で読み込ませています。両ツールで1つの辞書を共有できます。
書き方は、エージェントがそのまま rg に渡せる交替パターンにしました。エスケープ済みの形で置くのがポイントで、読んだ側が正規表現を組み直す必要をなくしています。
## 固有名詞の表記ゆれ(検索用辞書)
固有名詞で本文検索するときは、下記のパターンをそのまま rg か grep -E に渡す(grep -G では | と \+ の意味が変わる)。
表に無い固有名詞でも、カタカナ語はラテン表記の併存を疑い、単一表記で「無い」と結論しない。
タチバナ タチバナ|[Tt]achi[Bb]ana
モリノ モリノ|MORI\+|MORI+|[Mm]orino|MORINO
Power BI [Pp]ower ?BI|powerbi|pbip
Databricks のようにラテン表記しか存在しない語は載せていません。大小の差だけなので rg -i で足ります。辞書に載せるのは「機械的に推測できない対応」だけです。
辞書のパターンは「どのgrepに渡すか」で意味が変わる
この辞書を書いてから、もう1つ踏みました。Codexのレビューで「Claude Codeの既定の grep は -G、つまり基本正規表現(BRE)で動く」と指摘されて、確かめたら本当でした。
BREでは + は普通の文字で、| は選択ではありません。ripgrepや grep -E の拡張正規表現(ERE)と逆です。同じ6ファイルで比べるとこうなります。
$ rg --sort path -l "MORI\+"
notes/app.md
$ grep -rl "MORI\+" notes
notes/app.md
notes/noise.md
notes/zenkaku.md
$ rg --sort path -il "タチバナ|tachibana"
notes/contract.md
notes/meeting.md
notes/projects.md
$ grep -ril "タチバナ|tachibana" notes
grep のほうはClaude Codeのシェルで打ったもの(実体はugrep、-G 付き)で、最後のコマンドは0件です。
ripgrepで正しく動く MORI\+ は、BREのugrepでは「Iの1回以上の繰り返し」になって無関係な MORIII まで拾います。もっと痛いのは タチバナ|tachibana で、BREでは | がただの文字なので0件です。辞書を用意したのに、渡す先のgrepが違うと「見当たりません」に戻ります。
なので辞書の冒頭に「このパターンは rg か grep -E で使う」と1行足しました。macOSやLinuxのClaude Codeが既定でBashの grep を打つ以上、この1行が無いと辞書は半分しか効きません。
導入前後で、同じ語のヒット数がこう変わりました。
| 語 | 単一表記 | 辞書パターン |
|---|---|---|
| 製品A(カタカナ) | 130 | 278 |
| 製品B(カタカナ) | 1,171 | 1,331 |
| タチバナ | 193 | 243 |
| Power BI | 39 | 89 |
| モリノ | 335 | 354 |
製品Aは2倍以上、Power BIも2倍以上です。Power BIは PowerBI とスペース無しで書かれたものが最多で、スペース有りだけで探すと半分以上を取りこぼしていました。
実務でどこに効くか
自分の場合はナレッジのリポジトリでしたが、仕組みは業務のコードベースでも同じです。思い当たるところを4つ書きます。
「エージェントが見つけられなかった」を信じない
クライアント名、製品名、社内システムの略称。業務リポジトリはこの種の固有名詞だらけで、しかもSlackではカタカナ、コードでは英字、ドキュメントでは正式表記、と場所によって割れます。エージェントが「該当するファイルはありません」と言ったとき、それは「1つの表記で0件だった」と読み替える習慣をつけました。もう1回、別の表記で聞くだけで出てきます。
CLAUDE.md / AGENTS.md に辞書を置く
上に書いた形をそのまま持ち込めます。新しい固有名詞が出るたびに1行足すだけです。「ローマ字化で推測できない対応」と「正規表現のメタ文字を含む表記」を優先して載せると、少ない行数で効きます。以前にClaude Codeに文脈を渡す方法を書いたとき、CLAUDE.md は「振る舞いの指示」を書く場所だと思っていましたが、「探索のための辞書」を置く場所でもありました。
ファイル名と見出しは「人が読む」より「rgが当たる」で決める
雑記.md は最悪で、HealthLake_導入メモ.md は最良です。ファイル名は Glob と rg --files の当たりになり、見出しは Grep の content モードで前後の文脈ごと拾われます。各ディレクトリに _index.md を置いて「この中に何があるか」を正規表記で列挙しておくと、表記ゆれを正規表記へ直す名前解決の辞書として働きます。エージェント向けに書く文書は、人間向けの読みやすさより「検索語が本文に含まれているか」を優先したほうが結果的に役に立ちました。
リンク切れを表記ゆれの副作用として定期的に数える
存在しないパスへの参照は、表記ゆれの副作用として静かに溜まります。自分は月に1回、Markdown内の相対リンクを全部抜き出して実在確認するスクリプトを回すようにしました。
rg -o --no-filename '\]\(([^)#]+\.md)' -r '$1' -t md | sort -u | while read -r p; do
[ -e "$p" ] || echo "missing: $p"
done
上のワンライナーはリポジトリのルートからの相対パスだけを見る簡易版なので、各ファイルからの相対パスを正しく解決するには、ファイルごとにディレクトリを基準にする処理が要ります。手元では Python で書き直して使っています。
まとめ
Claude CodeもCodexも、索引ではなくgrepの仲間で探しています。macOSやLinuxのClaude Codeは既定でBash経由の同梱ugrep、Grep ツールを有効にすればripgrep、Codexは観測上 rg です(Codex内部の索引の有無は公開情報では確認できません)。これは鮮度と完全一致に強く、綴り違いに対して「0件」を返せる設計です。
その裏返しで、同じ対象が複数表記で書かれた文書に構造的に弱い。自分のリポジトリでは片方の表記だけで149ファイルを取りこぼし、存在しないパスを案内するスキルが数か月放置されていました。
対策は、辞書を「探すもの」ではなく「常に載っているもの」に置くこと。CLAUDE.md や AGENTS.md に、rg へそのまま渡せるエスケープ済みの交替パターンを書いておくだけで、ヒット数が2倍になる語がありました。
エージェントに「無い」と言われたとき、もう1つの表記で聞き直したことはありますか? もし出てきたら、その語は辞書に載せる候補です。

