[DOCS] Hooks docs do not explain `if: "Bash(...)"` matching for subshells, backticks, and variable expansions

Open 💬 1 comment Opened Jun 4, 2026 by coygeek

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, and PermissionDenied. On other events, a hook with if set 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 leading VAR=value assignments are stripped, so if: "Bash(git push *)" matches both FOO=bar git push and npm 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 like Bash(safe-cmd *) won't give it permission to run the command safe-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 hook if: "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:

Bash if rules are evaluated against parsed subcommands, including commands nested inside subshells ($(...), ( ... )) and backticks. Variable references such as $VAR no 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 $VAR or $(pwd)
  • if: "Bash(git push *)" should match echo 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗