[BUG] Permission pattern matching fails for Bash commands containing $() subshells
Description
The permission rule pattern matching incorrectly handles Bash commands containing $() subshells. When a $() appears as a standalone argument or after =, the pattern matcher fails and defaults to matching (i.e., the hook fires even though the command doesn't match the pattern).
This also affects the matcher field: "matcher": "Bash(foo*)" does not match foo at all, suggesting matcher only supports tool names without argument patterns.
Related: #37106 (broader permission pattern matching issue with special characters)
Reproduction
Hook configuration
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(foo*)",
"command": "echo 'BLOCKED' >&2 && exit 2"
}
]
}
]
}
}
Results
| Command | Expected | Actual |
|---------|----------|--------|
| foo | Blocked | Blocked ✅ |
| echo hello | Pass | Pass ✅ |
| echo "text $(echo hello) text" | Pass | Pass ✅ |
| echo $(date) | Pass | Blocked ❌ |
| echo --flag="$(echo value)" | Pass | Blocked ❌ |
| echo "a" "$(echo b)" | Pass | Blocked ❌ |
| ls "$(echo /tmp)" | Pass | Blocked ❌ |
Pattern
$()inside a single interpolated string → Pass:echo "text $(cmd) text"$()as a standalone argument or after=→ Incorrectly blocked:echo $(cmd),--flag="$(cmd)","a" "$(cmd)"
The pattern matcher appears to fail when parsing $() in these positions, and defaults to executing the hook (blocking) on parse failure.
Additional: matcher field does not support argument patterns
Changing the configuration to use "matcher": "Bash(foo*)" instead of "matcher": "Bash" + "if": "Bash(foo*)" causes the hook to never fire at all — not even for foo. This suggests matcher only matches tool names, not tool arguments.
Environment
- Claude Code version: 2.1.90
- OS: macOS (Darwin 24.6.0)
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗