Claude Media
Claude CodeにGlobとGrepが無い理由 — bfsとugrepがBash経由で動く仕組み

Claude CodeにGlobとGrepが無い理由 — bfsとugrepがBash経由で動く仕組み

macOS・Linux・WSLのClaude Codeは既定でGlobとGrepを持たず、find・grepを組み込みのbfs・ugrepとしてBashで動かします。hookと権限ルールからの見え方と、ツールを戻す条件を示します。

Claude CodeにGlobとGrepが無い理由 — bfsとugrepがBash経由で動く仕組み

ツール一覧にはGlobとGrepが載っているのに、macOSやLinuxのセッションでは呼ばれない。PreToolUseのmatcherにGrepと書いたhookが一度も発火しない。原因は不具合ではなく仕様です。

macOS・Linux・WSLでは、Claude CodeはGlobとGrepを既定のツールセットから外しています。Claudeはその代わりにBashツールでfindとgrepを実行し、Claudeのシェルの中ではこの2つが組み込み版のbfsとugrepで動きます。Windowsでは、2つとも既定のツールセットに入っています。

この仕様のせいで、検索はhookにも権限ルールにもBashの呼び出しとして届きます。GrepやGlobを名指しした設定は、既定のままでは何にも当たりません。

検索がBash呼び出しとして見えると、何が変わるのか

影響範囲

設定の当たり方が変わる3か所

  • hookのmatcher

    GrepやGlobを指定したPreToolUseは対象のツール呼び出しが無いので発火しません。検索を捕まえるならBashのmatcherで、コマンド文字列を見ます。

  • 権限ルール

    findとgrepは組み込みの読み取り専用コマンドに入っており、Bashの許可リストに書かなくても確認なしで動きます。止めたいならaskかdenyのルールを足します。

  • Readのdenyルール

    GrepとGlobにはReadのdenyルールが効きますが、Bash経由の検索では効き方が変わります。.envを守る目的ならサンドボックスまで視野に入ります。

3つとも根は同じです。ツールの種類がGrepからBashに変わったので、ツール名で絞る設定が外れます。

手元のClaude Codeでは、find・grepはシェル関数になっていた

仕様の文面だけでなく、実際にシェルの中がどうなっているかを見ておきます。v2.1.301のmacOS(zsh)でClaude Codeのセッション内から確かめた結果です。

grep --version | head -1
find --version | head -1
type grep

出力は次の形でした(1行目のみ抜粋、パスは省略)。

ugrep 7.8.4 aarch64-apple-macosx ...
bfs 4.1.1
grep is a shell function from ...

grepとfindは、システムの実行ファイルではなくシェル関数でした。関数の中身を見ると、Claude Code自身の実行ファイルを別名で呼び直しています。zshでは次のような形です(引数の素通し部分は省いた要約)。

# grep の中身(要約)
ARGV0=ugrep <claude実行ファイル> -G --ignore-files --hidden -I \
  --exclude-dir=.git --exclude-dir=.svn --exclude-dir=.hg ... "$@"
 
# find の中身(要約)
ARGV0=bfs <claude実行ファイル> -S dfs -regextype findutils-default "$@"

読み取れることは3つあります。

  • 別途インストールしたツールではなく、Claude Codeの実行ファイルがugrep・bfsとして振る舞っています。システムのgrepやfindは使われません
  • grepには-G(基本正規表現)、--ignore-files(.gitignoreなどに従う)、--hidden、-I(バイナリを除外)が既定で付きます。.gitなどのバージョン管理ディレクトリは既定で除外されます
  • -zや--filterなど一部のオプションを含む呼び出しでは、関数は組み込み版ではなくシステムのgrepにそのまま渡します

これは2.1.301のmacOSで見えた実装です。関数の中身やオプションは版で変わり得るので、自分の環境ではtype grepで確かめてください。なお、同じ仕組みでrgも組み込み版を呼ぶ関数になっていました。

正規表現の書き方はPOSIX寄りのままになる

grepが-Gで動くので、波括弧は普通の文字として扱われます。手元でa{b}という行を含むファイルに対し、grep 'a{b}'は1行に一致し、grep -E 'a\{b\}'も一致しました。

一方、実体のGrepツールはripgrep製で、パターンの書式もripgrep流です。Goのinterface{}を探すにはinterface\{\}とエスケープします。ツールを戻すと正規表現の癖が変わる点は、後の節でも触れます。

hookで検索を捕まえるにはBashのmatcherを使う

PreToolUseは、ツール名で絞れるイベントです。matcherにはBash・Read・Glob・Grepなど組み込みツールの名前を書けます。ただ、検索がBash経由になったセッションでは、Grepと書いたmatcherに当たる呼び出しが存在しません。

検索を記録したいなら、Bashのmatcherで受けて、標準入力のJSONからtool_input.commandを取り出します。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/log-search.sh" }
        ]
      }
    ]
  }
}
#!/bin/bash
# log-search.sh — find / grep で始まるコマンドだけ記録する例
CMD=$(jq -r '.tool_input.command')
case "$CMD" in
  find\ *|grep\ *) echo "$CMD" >> ~/.claude/search.log ;;
esac
exit 0

これは構成の一例です。cd src && grep -r foo .のように複合コマンドになると先頭がgrepにならず、caseの判定をすり抜けます。パイプや&&の後ろまで見たいときは、コマンド全体を正規表現で見る形に広げます。

GrepツールとGlobツールの入力項目も押さえておきます。Grepはpattern・path・glob・output_modeなどを、Globはpatternをtool_inputに持ちます。ツールを戻した構成でhookを書くなら、Bashとは別のmatcherを足す形になります。

macOSとWindowsで同じhook設定を共有するとき

Windowsでは、GlobとGrepが既定のツールセットに残っています。macOSとWindowsの両方で同じsettings.jsonを使うチームでは、BashだけのmatcherだとWindowsの検索を拾えません。

matcherの値は、英数字・_・-・空白・,・|だけなら正確な名前の一覧として扱われ、Bash|Grep|Globと書くとこの3つのどれにも一致します。それ以外の文字を含むと、先頭や末尾を固定しない正規表現として評価されます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Grep|Glob",
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/log-search.sh" }
        ]
      }
    ]
  }
}

同じmatcherでもBashの呼び出しはtool_input.commandを、Grepはpatternを持つので、スクリプトはtool_nameを見て読む項目を分けます。上のlog-search.shを共有するなら、jq -r '.tool_name'で分岐する形に広げます。

権限ルールの見え方 — 確認なしで動き、止めるには明示が要る

権限の側にも、見え方の変化があります。ls・cat・head・tail・grep・find・wcなどは読み取り専用コマンドとして、確認なしで実行される組み込みの集合に入っています。この集合は設定で変えられません。

つまり、ClaudeがBash経由で検索しても、Grepツールと同様に確認は出ません。止めたいコマンドがあるときは、そのコマンドにaskかdenyのルールを足します。

{
  "permissions": {
    "ask": ["Bash(grep *)"]
  }
}

例外もあります。Manualモードでは、findのように書き込み系・実行系のフラグを持つコマンドは、クォートされていないglobを含むと確認が出ます。-deleteのようなフラグにglobが展開される恐れがあるためです。

Readのdenyルールが届く範囲

秘密ファイルを守る目的でRead(./.env)のdenyルールを書いている場合は、検索経路ごとに届き方が違います。

経路Readのdenyルール
ReadツールReadのdenyルール適用される
GrepツールとGlobツールReadのdenyルール可能な範囲で適用される
Bashのcat・head・tail・sed・teeとリダイレクトReadのdenyルール認識できる形なら適用される
grep -r pattern .のようにファイルを名指ししない実行Readのdenyルール適用されない
PythonやNodeのスクリプトによる間接的な読み取りReadのdenyルール適用されない

Bash経由の検索が主役になると、grep -rが表の4行目に当たる場面が増えます。ファイルを一切読ませたくないなら、OSレベルで効くサンドボックスまで含めて設計します。

権限とサンドボックスは別の層です。権限ルールはBashやReadを含む全ツールの使用可否とアクセス先を決め、サンドボックスはシェルコマンドとその子プロセスのファイル・ネットワークアクセスをOSの側で制限します。サンドボックスの設定に書いたパスと、権限ルールのパスは最終的な設定に統合されます。プロンプトインジェクションでClaudeの判断をすり抜けられても、サンドボックスの制限は残ります。Read(./.env)のdenyルールを書いたうえで、サンドボックスも有効にしておく二重の構えです。有効にする手順と設定キーはサンドボックスのページにあるので、使う前に自分の版の記載を確かめてください。

もう1点、Glob(docs/**)のようなGlobへのパス指定ルールは受け付けられても参照されず、起動時に警告が出ます。パスで絞りたいときはRead(docs/**)と書きます。

GlobとGrepを戻す条件と、戻したときの違い

戻す方法は何通りかあります。フラグごとの細かい書き分けは--toolsフラグの解説に詳しく、ここでは条件だけを並べます。

戻す条件戻るもの備考
--toolsにGlobかGrepを書く戻るもの書いたほうだけ備考Agent SDKの同等のオプションでも同じ
--allowedToolsに片方を書く戻るもの両方備考設定ファイルの許可ルールにこの効果は無い
Bashをセッションから外す戻るもの両方備考denyルール・--disallowedTools・--restricted
サブエージェントのtoolsにGlob/Grepを書き、Bashを書かない戻るものそのサブエージェント備考--agentで起動すればセッション全体

--toolsで名前を明示したときに戻る挙動は、v2.1.162で入っています。それ以前は、これらの名前が黙って無視されていました。

戻すと、検索の中身も入れ替わります。

項目Bash経由(ugrep・bfs)実体のGrep・Glob
検索エンジンBash経由(ugrep・bfs)組み込みのugrep(-Gが既定)実体のGrep・GlobGrepはripgrep
gitignoreの扱いBash経由(ugrep・bfs)--ignore-filesで.gitignoreに従う実体のGrep・GlobGrepは従う、Globは既定で従わない
結果の上限Bash経由(ugrep・bfs)コマンド出力の上限に従う実体のGrep・GlobGlobは更新日時順で100ファイル
hook・権限での見え方Bash経由(ugrep・bfs)Bashとして届く実体のGrep・GlobGrep・Globとして届く

Globを.gitignoreに従わせたいときは、起動前にCLAUDE_CODE_GLOB_NO_IGNORE=falseを設定します。gitignoreまわりの挙動の詳細はrespectGitignoreとGlob・Grepの違いにまとまっています。Globのタイムアウトを調整したい場合はCLAUDE_CODE_GLOB_TIMEOUT_SECONDSの解説を参照してください。

hookが発火しないときの切り分け

「Grepのhookが動かない」「Globへの許可ルールが効かない」と感じたとき、原因の候補は次の順に絞れます。

症状最初に疑うこと
Grep・Globのmatcherが発火しない最初に疑うことmacOS・Linux・WSLの既定でツールが無い。Bashのmatcherに切り替える
Bashのmatcherでも検索を拾えない最初に疑うこと複合コマンドやパイプで先頭がgrep・findにならない
Readのdenyがあるのに中身を検索された最初に疑うことgrep -rのようにファイルを名指ししない実行。サンドボックスを検討
WindowsだけBashのmatcherで検索を拾えない最初に疑うことWindowsはGrep・Globが既定で残る。Bash|Grep|Globのmatcherにする
grepの結果が手元のシェルと違う最初に疑うこと-I・--ignore-files・--hiddenの既定が付いた組み込みugrepになっている
コンテナ内でgrepが動かない最初に疑うことugrep側の不具合の可能性。amd64コンテナの事例を参照

経緯と、どちらを選ぶかの判断材料

この構成はv2.1.117で入りました。changelogには、macOSとLinuxのネイティブビルドでGlobとGrepのツールが組み込みのbfsとugrepに置き換わり、別ツールの往復が要らなくなる、と書かれています。Windowsとnpmでインストールしたビルドは変わりません。当時の整理はv2.1.117のリリースノートにあります。その後もv2.1.235で、組み込みgrepが異常なパターンでメモリを使い切らず早めに失敗するよう改善されています。

戻すかどうかは、hookと権限をどう使っているかで決まります。

  • 検索の頻度や対象をログに残したいだけなら、Bashのmatcherで足ります。既定のままでよい構成です
  • hookや権限のルールをGrep・Globの名前で書いた資産が既にあるなら、--toolsか--allowedToolsで戻すと、書き換えずに動きます
  • 検索エンジンの差(ugrepとripgrepの正規表現の癖)で結果が変わると困るなら、どちらかに揃えます。セッションごとに混在させると、同じパターンが片方で通らない事態が起きます

迷ったら、まず既定の構成でBashのmatcherを試し、足りない部分だけをツールを戻す側で補う順序が、設定の差し替えが少なく済みます。

まとめ

macOS・Linux・WSLでGlobとGrepが見えないのは、v2.1.117以降の既定仕様です。検索はBashのfind・grepとして動き、hookも権限ルールもBashの目で見ます。GrepやGlobの名前で書いた設定は、ツールを戻さない限り空振りします。

手元のv2.1.301では、grepとfindはClaude Code自身をugrep・bfsの別名で呼ぶシェル関数でした。正規表現は-Gの基本正規表現で、gitignoreに従い、隠しファイルは含め、バイナリは除きます。設定を直す前に、type grepで自分の環境の実体を確かめておくと、原因の切り分けが早く済みます。

この記事を共有:XはてブLinkedIn