[BUG] Hook `if` filters falsely match Bash commands containing brace expansion
Summary
A PreToolUse Bash hook with an if clause using permission rule syntax (e.g. "if": "Bash(git push*)") incorrectly matches commands that contain shell brace expansion ({a,b,c}), even when the command has nothing to do with the rule's prefix. This causes the hook to fire and (in our case) emit a misleading permissionDecision: "ask" prompt for unrelated commands.
This appears to be the inverse symptom of #46785 / #41443 (closed as duplicate). Those issues report permissions.allow rules failing to match braced commands. We're observing if filters falsely matching braced commands — almost certainly the same root cause (AST parser bails on brace expansion) but a distinct user-visible bug in a different code path.
Related open issue: #43713 (sandbox auto-allow bypassed by shell expansions — same parser, different symptom).
Reproduction
~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"Confirm git commit before executing\"}}'",
"if": "Bash(git commit*)"
},
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"Confirm git push before executing\"}}'",
"if": "Bash(git push*)"
}
]
}
]
}
}
Ask Claude to run any Bash command containing brace expansion — for example:
grep -E "Foo|Bar" /Users/me/proj/{a,b,c}/*.tsx 2>&1
ls /tmp/{settings.json,debug.log}
Expected: Neither if matches (neither command has "git commit" or "git push" anywhere). Both hooks should be skipped.
Actual: Both if clauses match. The user sees a prompt like "Confirm git push before executing" for an innocent grep/ls.
How I narrowed it down
I temporarily replaced the two ask-emitting hooks with logging-only hooks (preserving the if clauses) plus a third no-if control hook. Then I ran a series of canary commands. Results:
| Command | NO-IF (control) | COMMIT-IF | PUSH-IF |
|---|---|---|---|
| grep -E "foo" /tmp/file (plain) | fires | — | — |
| grep -E "a\|b" /tmp/file 2>&1 (quoted pipe + redirect) | fires | — | — |
| ls /tmp/file (plain) | fires | — | — |
| echo "git pushpullpop" > /dev/null (substring) | fires | — | — |
| echo "running git push diagnostic" (substring in quotes) | fires | — | — |
| ls /tmp/{a,b} (brace expansion) | fires | false match | false match |
Quoted pipes, redirections, and substrings of "git push" in echoed strings do not trigger false matches. Only brace expansion does, and it triggers every if clause regardless of its prefix.
Workaround
Move the matching into the hook command itself, sidestepping the permission matcher:
{
"type": "command",
"command": "cmd=$(jq -r '.tool_input.command'); echo \"$cmd\" | grep -qE '(^|[;&|])[[:space:]]*git[[:space:]]+push([[:space:]]|$)' && echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"Confirm git push before executing\"}}'; exit 0"
}
This works correctly for all the cases above, including brace expansion.
Environment
- Claude Code CLI, macOS (Darwin 25.5.0, Apple Silicon)
- Bash,
defaultMode: "auto"
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗