Files
gh-glittercowboy-taches-cc-…/skills/create-hooks/references/matchers.md
2025-11-29 18:28:37 +08:00

471 lines
7.3 KiB
Markdown

# 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.