Bash permission rules are quote-blind: an operator inside a quoted argument defeats a matching rule

Status Open
Reported on v2.1.274
Maintainer reply None cached
Activity 2 comments · opened Sep 22, 2026

Summary

Bash(...) permission rules are matched against the raw command string without regard to shell quoting, so a shell operator inside a quoted argument (a > in an awk program, a && in a git commit -m message, a | in a grep pattern) defeats a rule that should match and forces a prompt. Conversely, a shell variable or $(...) anywhere in the command falls through the rule to the classifier, even when the variable is inert. The docs say Claude Code "is aware of shell operators" for compound commands, but the awareness stops at operators inside quotes.

Reproduction

With Bash(awk *) in permissions.allow (default permission mode):

awk '{print NR}' file        # runs, no prompt
awk 'NR>=300 && NR<=430' file   # prompts: "uses shell operators that require approval"

The second command has no shell operator. > and && are inside a single-quoted awk program and never reach the shell.

Same class:

git commit -m "Fix a && b"          # prompts on the && inside the message
grep -n 'a|b' file                  # prompts on the | inside the pattern
gh pr create --body "$(cat x.md)"   # falls through a matching Bash(gh pr create *) allow

Impact

In a repository with ~250 Bash rules we deleted Bash(awk *) outright because it could not be trusted to match, and we maintain a CLAUDE.md rule telling the model to rewrite awk as sed -n / cut to avoid the operator. Any rule whose command routinely takes a quoted program or free text (awk, sed, jq, grep -E, git commit -m, gh ... --body) has the same problem. The workaround we converged on is a PreToolUse hook with a hand-written shell tokenizer (about 400 lines) that decides these commands instead of the rules, which is a lot of machinery to replace a quote-aware tokenizer.

Expected

Rule matching should tokenize the command the way the shell will: a quoted argument is one word, and an operator inside it is data. A rule matching the command word should then match regardless of what its quoted arguments contain. Variable expansion is harder, but a plain $VAR (no substitution) in argument position should not by itself void a matching rule.

Environment

Claude Code 2.1.274, macOS (Darwin 25.2), default permission mode auto, seatbelt sandbox enabled.

Related

  • #79759 asks for specificity-aware precedence between rules; this is a different gap (the match itself, not its precedence).
  • The docs page permissions.md section "What a Bash rule doesn't match" describes operator splitting but not quoting.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗