[BUG] Bash permission matcher misparses operators inside quoted arguments — a quoted | in a grep pattern defeats Bash(grep:*) and forces a prompt

Resolved 💬 2 comments Opened Jul 12, 2026 by metapiece Closed Jul 15, 2026

Preflight Checklist

  • [x] I searched existing issues — this is a deliberately minimal, isolated case within the permissions-matching cluster (#16561, #27688, #30519, #43713); see "Relationship to existing issues" below.
  • [x] Single bug report.
  • [x] Using the latest version.

What's wrong?

With a wildcard allow rule Bash(grep:*) in settings.local.json, a single grep command whose pattern contains a | (regex alternation) — e.g. grep -E "foo|bar" file.txt — still triggers a permission prompt, even though the rule should cover any grep invocation.

The permission matcher appears to split the command on shell control operators (|, ;, &&) to check each component against the allowlist (cf. #16561), but the split is not shell-quote-aware: the | inside the quoted regex is treated as a pipe operator. The command splits into fragments like grep -E "foo and bar", neither of which matches Bash(grep:*), so Claude Code falls back to prompting.

The same defect affects any allowlisted command with a control character inside a quoted argument, e.g.:

  • gh pr view 1 --jq '.[] | .body' — pipe inside a jq string
  • sed 's/a|b/c/' file — pipe inside a sed script
  • awk '/x/{a} ; /y/{b}' file — semicolon inside an awk program

Steps to reproduce (minimal, macOS)

  1. In a project, set .claude/settings.local.json:

``json
{ "permissions": { "allow": ["Bash(grep:*)"] } }
``

  1. Use the default permission mode (not bypassPermissions).
  2. Have Claude run grep -E "foo|bar" somefile.txta permission prompt appears (the bug).
  3. Have Claude run grep -E "foobar" somefile.txt → auto-approved (correct).
  4. Have Claude run grep -e foo -e bar somefile.txt → auto-approved (correct).

The only difference between (3) and (4)/(5) is the literal | inside the quoted pattern.

Expected behavior

A single grep … command should match Bash(grep:*) regardless of characters inside its quoted arguments. Component-splitting for permission checks should honor shell quoting: a | / ; / && inside single or double quotes is data, not an operator. (Reference: Python's shlex(command, punctuation_chars=True) yields operators as their own tokens while leaving quoted text intact — a quoted | stays part of its token.)

Impact

  • Frequent, needless permission prompts for very common commands (grep alternation; jq/awk/sed with operators in their scripts) despite explicit wildcard allow rules.
  • Severe for headless / background / autonomous (Agent SDK) sessions: a permission prompt with no interactive approver is not a quick click — it becomes an indefinite hang. Teams are writing PreToolUse hooks to hard-deny such commands specifically to convert the hang into a fast failure — a workaround for this matcher gap.

Environment

  • Claude Code 2.1.205
  • macOS (darwin)

Relationship to existing issues

This is intentionally a single-command, non-compound reproducer to isolate the quote-awareness defect in the command splitter, distinct from:

  • #16561 — "Parse compound Bash commands and match each component against permissions": the component-matching this bug is about; the split needs to be quote-aware.
  • #27688 — bundles a genuinely compound command + quoted Windows path + .exe + Always-Allow persistence (several confounded factors).
  • #30519 / #43713 — broader permissions-matching problems.

Providing a minimal reproducer in the hope it helps triage the specific quote-awareness fix.

View original on GitHub ↗

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