[DOCS] Hooks docs do not explain `if: "Bash(...)"` matching for subshells, backticks, and variable expansions
Documentation Type
Incorrect/outdated documentation
Documentation Location
https://code.claude.com/docs/en/hooks
Section/Topic
if hook handler field and Bash matching behavior for tool-event hooks
Current Documentation
The hooks page currently says:
|if| no | Permission rule syntax to filter when this hook runs, such as"Bash(git *)"or"Edit(*.ts)". The hook only spawns if the tool call matches the pattern, or if a Bash command is too complex to parse. Only evaluated on tool events:PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequest, andPermissionDenied. On other events, a hook withifset never runs. Uses the same syntax as permission rules |
It also says:
For Bash, the rule is matched against each subcommand of the tool input after leadingVAR=valueassignments are stripped, soif: "Bash(git push *)"matches bothFOO=bar git pushandnpm test && git push. The hook runs if any subcommand matches, and always runs when the command is too complex to parse.
The permissions page cross-reference currently says:
Claude Code is aware of shell operators, so a rule likeBash(safe-cmd *)won't give it permission to run the commandsafe-cmd && other-cmd. The recognized command separators are&&,||,;,|,|&,&, and newlines. A rule must match each subcommand independently.
What's Wrong or Missing?
The v2.1.163 changelog says:
Fixed hookif: "Bash(...)"conditions firing on every Bash command containing$()or$VAR; the pattern now matches against commands inside subshells and backticks too
The live docs do not reflect that behavior change.
A. The hooks page does not mention the previously buggy cases
The current if documentation explains compound commands and leading VAR=value stripping, but it does not tell readers how if: "Bash(...)" behaves when the Bash input contains command substitution like $(), variable references like $VAR, backtick command substitution, or nested commands inside subshells.
B. The current wording can mislead hook authors after v2.1.163
The docs currently imply that Bash matching is fully described by subcommand splitting plus the "too complex to parse" fallback. After v2.1.163, there is a specific documented behavior change worth calling out: hook if filters should no longer spuriously fire just because a Bash command contains $() or $VAR, and matching now reaches commands inside subshells and backticks.
C. The permissions cross-reference is incomplete for hook authors
The hooks page sends readers to the permissions syntax page, but the permissions page currently documents separators, wrappers, and some fragile argument cases without covering subshells, backticks, or variable-expansion-related matching semantics. That leaves the hook behavior under-specified across both pages.
Suggested Improvement
Update the hooks page's if field documentation with an explicit Bash note for v2.1.163 behavior, for example:
Bashifrules are evaluated against parsed subcommands, including commands nested inside subshells ($(...),( ... )) and backticks. Variable references such as$VARno longer cause the filter to match unrelated commands on their own. If parsing fails, the hook still runs as a fallback.
Also update the permissions page's Bash matching section so the shared rule syntax reference covers the same cases.
Useful examples to add:
if: "Bash(git status)"should not match an unrelated command just because the shell input contains$VARor$(pwd)if: "Bash(git push *)"should matchecho before && $(printf 'git push origin main')only when the parsed nested command actually matches the rule- A note clarifying what still counts as "too complex to parse"
Impact
Medium - Makes feature difficult to understand
Additional Context
Affected Pages:
| Page | Line(s) | Context |
|------|---------|---------|
| https://code.claude.com/docs/en/hooks | 300-305 | if field semantics for tool-event hooks and Bash subcommand matching |
| https://code.claude.com/docs/en/permissions | 125-139 | Shared Bash permission-rule parsing notes for compound commands and wrappers |
Total scope: 2 pages affected
This appears to be a documentation update required by the v2.1.163 changelog entry, not a separate feature request.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗