# Matchers and Pattern Matching Complete guide to matching tools with hook matchers. ## What are matchers? Matchers are regex patterns that filter which tools trigger a hook. They allow you to: - Target specific tools (e.g., only `Bash`) - Match multiple tools (e.g., `Write|Edit`) - Match tool categories (e.g., all MCP tools) - Match everything (omit matcher) --- ## Syntax Matchers use JavaScript regex syntax: ```json { "matcher": "pattern" } ``` The pattern is tested against the tool name using `new RegExp(pattern).test(toolName)`. --- ## Common Patterns ### Exact match ```json { "matcher": "Bash" } ``` Matches: `Bash` Doesn't match: `bash`, `BashOutput` ### Multiple tools (OR) ```json { "matcher": "Write|Edit" } ``` Matches: `Write`, `Edit` Doesn't match: `Read`, `Bash` ### Starts with ```json { "matcher": "^Bash" } ``` Matches: `Bash`, `BashOutput` Doesn't match: `Read` ### Ends with ```json { "matcher": "Output$" } ``` Matches: `BashOutput` Doesn't match: `Bash`, `Read` ### Contains ```json { "matcher": ".*write.*" } ``` Matches: `Write`, `NotebookWrite`, `TodoWrite` Doesn't match: `Read`, `Edit` Case-sensitive! `write` won't match `Write`. ### Any tool (no matcher) ```json { "hooks": { "PreToolUse": [ { "hooks": [...] // No matcher = matches all tools } ] } } ``` --- ## Tool Categories ### All file operations ```json { "matcher": "Read|Write|Edit|Glob|Grep" } ``` ### All bash tools ```json { "matcher": "Bash.*" } ``` Matches: `Bash`, `BashOutput`, `BashKill` ### All MCP tools ```json { "matcher": "mcp__.*" } ``` Matches: `mcp__memory__store`, `mcp__filesystem__read`, etc. ### Specific MCP server ```json { "matcher": "mcp__memory__.*" } ``` Matches: `mcp__memory__store`, `mcp__memory__retrieve` Doesn't match: `mcp__filesystem__read` ### Specific MCP tool ```json { "matcher": "mcp__.*__write.*" } ``` Matches: `mcp__filesystem__write`, `mcp__memory__write` Doesn't match: `mcp__filesystem__read` --- ## MCP Tool Naming MCP tools follow the pattern: `mcp__{server}__{tool}` Examples: - `mcp__memory__store` - `mcp__filesystem__read` - `mcp__github__create_issue` **Match all tools from a server**: ```json { "matcher": "mcp__github__.*" } ``` **Match specific tool across all servers**: ```json { "matcher": "mcp__.*__read.*" } ``` --- ## Real-World Examples ### Log all bash commands ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.command' >> ~/bash-log.txt" } ] } ] } } ``` ### Format code after any file write ```json { "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|NotebookEdit", "hooks": [ { "type": "command", "command": "prettier --write $CLAUDE_PROJECT_DIR" } ] } ] } } ``` ### Validate all MCP memory writes ```json { "hooks": { "PreToolUse": [ { "matcher": "mcp__memory__.*", "hooks": [ { "type": "prompt", "prompt": "Validate this memory operation: $ARGUMENTS\n\nCheck if data is appropriate to store.\n\nReturn: {\"decision\": \"approve\" or \"block\", \"reason\": \"why\"}" } ] } ] } } ``` ### Block destructive git commands ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/path/to/check-git-safety.sh" } ] } ] } } ``` `check-git-safety.sh`: ```bash #!/bin/bash input=$(cat) command=$(echo "$input" | jq -r '.tool_input.command') if [[ "$command" == *"git push --force"* ]] || \ [[ "$command" == *"rm -rf /"* ]] || \ [[ "$command" == *"git reset --hard"* ]]; then echo '{"decision": "block", "reason": "Destructive command detected"}' else echo '{"decision": "approve", "reason": "Safe"}' fi ``` --- ## Multiple Matchers You can have multiple matcher blocks for the same event: ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/path/to/bash-validator.sh" } ] }, { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "/path/to/file-validator.sh" } ] }, { "matcher": "mcp__.*", "hooks": [ { "type": "command", "command": "/path/to/mcp-logger.sh" } ] } ] } } ``` Each matcher is evaluated independently. A tool can match multiple matchers. --- ## Debugging Matchers ### Enable debug mode ```bash claude --debug ``` Debug output shows: ``` [DEBUG] Getting matching hook commands for PreToolUse with query: Bash [DEBUG] Found 3 hook matchers in settings [DEBUG] Matched 1 hooks for query "Bash" ``` ### Test your matcher Use JavaScript regex to test patterns: ```javascript const toolName = "mcp__memory__store"; const pattern = "mcp__memory__.*"; const regex = new RegExp(pattern); console.log(regex.test(toolName)); // true ``` Or in Node.js: ```bash node -e "console.log(/mcp__memory__.*/.test('mcp__memory__store'))" ``` ### Common mistakes ❌ **Case sensitivity** ```json { "matcher": "bash" // Won't match "Bash" } ``` ✅ **Correct** ```json { "matcher": "Bash" } ``` --- ❌ **Missing escape** ```json { "matcher": "mcp__memory__*" // * is literal, not wildcard } ``` ✅ **Correct** ```json { "matcher": "mcp__memory__.*" // .* is regex for "any characters" } ``` --- ❌ **Unintended partial match** ```json { "matcher": "Write" // Matches "Write", "TodoWrite", "NotebookWrite" } ``` ✅ **Exact match only** ```json { "matcher": "^Write$" } ``` --- ## Advanced Patterns ### Negative lookahead (exclude tools) ```json { "matcher": "^(?!Read).*" } ``` Matches: Everything except `Read` ### Match any file operation except Grep ```json { "matcher": "^(Read|Write|Edit|Glob)$" } ``` ### Case-insensitive match ```json { "matcher": "(?i)bash" } ``` Matches: `Bash`, `bash`, `BASH` (Note: Claude Code tools are PascalCase by convention, so this is rarely needed) --- ## Performance Considerations **Broad matchers** (e.g., `.*`) run on every tool use: - Simple command hooks: negligible impact - Prompt hooks: can slow down significantly **Recommendation**: Be as specific as possible with matchers to minimize unnecessary hook executions. **Example**: Instead of matching all tools and checking inside the hook: ```json { "matcher": ".*", // Runs on EVERY tool "hooks": [ { "type": "command", "command": "if [[ $(jq -r '.tool_name') == 'Bash' ]]; then ...; fi" } ] } ``` Do this: ```json { "matcher": "Bash", // Only runs on Bash "hooks": [ { "type": "command", "command": "..." } ] } ``` --- ## Tool Name Reference Common Claude Code tool names: - `Bash` - `BashOutput` - `KillShell` - `Read` - `Write` - `Edit` - `Glob` - `Grep` - `TodoWrite` - `NotebookEdit` - `WebFetch` - `WebSearch` - `Task` - `Skill` - `SlashCommand` - `AskUserQuestion` - `ExitPlanMode` MCP tools: `mcp__{server}__{tool}` (varies by installed servers) Run `claude --debug` and watch tool calls to discover available tool names.