Claude Codeで必ず高速なGrep,Globツールを使うようにして低速なfind/grepコマンドを実行しないようにする

Claude Codeが低速な grep/find コマンドを使ってしまう問題
hooks→PreToolUse / hooks→PostToolUse でツールの使用具合を監視する
settings.json に以下の設定を追加し続けて次の2つのファイルを新規作成します
~/.claude/settings.json
{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$HOME\"/.claude/hooks/log-tool-usage.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Grep|Glob",
"hooks": [
{
"type": "command",
"command": "\"$HOME\"/.claude/hooks/log-fast-tool-usage.sh"
}
]
}
]
}~/.claude/hooks/log-fast-tool-usage.sh
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
LOG="$HOME/.claude/logs/search-tool-usage.jsonl"
mkdir -p "$(dirname "$LOG")"
jq -nc --arg ts "$(date -u +%FT%TZ)" --arg tool "$TOOL" \
'{ts:$ts, type:"fast", tool:$tool}' >> "$LOG"
exit 0
~/.claude/hooks/log-tool-usage.sh
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
LOG="$HOME/.claude/logs/search-tool-usage.jsonl"
mkdir -p "$(dirname "$LOG")"
if echo "$COMMAND" | grep -Eq '(^|[;&|]\s*)(grep|egrep|fgrep|find)\b'; then
jq -nc --arg ts "$(date -u +%FT%TZ)" --arg cmd "$COMMAND" \
'{ts:$ts, type:"slow", tool:"Bash", command:$cmd}' >> "$LOG"
fi
exit 0このファイルを設置すると、クロードコードでツールを実行するたびに、次の箇所にログがたまります。
ここに find や grep などの遅いツールが使われているかどうかをまず確認します。
もし使われているようなら次の工程でそれらを高速ツールに置き換えましょう
Claude Codeに作業を任せていて、少し気になったことがありました。
「これまでの作業でfindやgrepを直接使ったか」と聞くと、かなりの確率でBash経由の遅いfind/grepを使っていることがわかりました。
Claude Codeには元々ripgrepベースの高速なGrepツールとGlobツールが組み込まれているのですが、これらは使われず、素のfind/grepコマンドが実行されてしまいます。
なぜ遅いコマンドが選ばれてしまうのか
理由は単純で、Claude Codeはgrepやfindを組み込みのread-onlyコマンドとして扱っており、Bash経由でも許可プロンプトなしにそのまま実行できてしまいます。
一方、専用のGrep/Globツールは.gitignoreを尊重し、globやtypeで絞り込めるなど本来はこちらの方が高速かつ的確なのですが、選ばせない仕組みが特にないため、習慣的にBash経由のコマンドが選ばれてしまう、という状態でした。
CLAUDE.mdに「Grepツールを使うこと」と書くだけでは弱いです。
パーミッションルールやhookはClaude Code自体が強制するものであり、CLAUDE.mdの指示はあくまで「促す」だけで「強制」はしない
とのことなので、確実に矯正するにはPreToolUseフックでBash経由のfind/grepをブロックし、正しいツールへ誘導する方法を取ることにしました。
フックスクリプトを作る
.claude/hooks/prefer-fast-search.shを作成します。
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -Eq '(^|[;&|]\s*)(grep|egrep|fgrep|find)\b'; then
echo "grep/find ではなく、組み込みの Grep ツール(ripgrep ベース)または Glob ツールを使ってください。" >&2
exit 2
fi
exit 0
実行権限を付けます。
chmod +x .claude/hooks/prefer-fast-search.sh
.claude/settings.jsonに登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prefer-fast-search.sh"
}
]
}
]
}
}
動作の仕組み
Bashツール呼び出し
│
▼
grep/find を含む?
│ │
Yes No
│ │
▼ ▼
ブロック(exit 2) そのまま実行
理由をClaudeへ返す
│
▼
Grep/Globツールへ
切り替えて再試行
ポイントはexit 2です。stderrに書いたメッセージがそのままClaudeへのフィードバックとして返るため、ブロックされた瞬間に「正しいツールを使うべきだ」とClaudeが認識し、切り替えてくれます。
permissions.denyでBashのgrep/find自体を禁止する方法も考えましたが、この場合Claudeに返るのは単なる「拒否されました」だけで、代替手段が示されません。フックなら理由を添えられる分、こちらの方が確実でした。
料金への影響は?
フック追加によるコスト増はあるのか?
type: "command"のフックはただのシェルスクリプト実行であり、モデル呼び出しを伴わないため、フック自体には課金が発生しない- ブロック時に拒否理由が会話履歴に載るため、その分だけ多少コンテキストは増える(数十トークン程度)
- とはいえ、非効率な
find/grepの大きな出力を毎回読み込むコストと比べれば誤差程度で、セッションが進むほどClaudeが学習して最初からGrep/Globを選ぶようになるため、トータルではむしろ削減方向に働く
type: "prompt"やtype: "agent"のフックは判断のために別途モデルを呼ぶため課金対象ですが、今回のような単純なコマンドチェックにはcommandタイプで十分でした。