[BUG] Bash permission pattern breaks when command contains parentheses

Status Fixed / completed
Reported on v2.1.72
Maintainer reply ✓ Yes — bcherny
Activity 7 comments · opened Mar 10, 2026 · closed Apr 9, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Any ( or ) character in a Bash command causes the permission allow-list matching to fail, even when the command should match a Bash(... **) pattern.

What Should Happen?

Parentheses in command strings should be treated as literal characters during permission matching. For example, with "Bash(cat **)" in the allow list, cat 'hello()' should be auto-allowed just like cat 'hello' is.

Error Messages/Logs

Steps to Reproduce

  1. Add "Bash(cat **)" to the permissions.allow list in .claude/settings.json:
  {
    "permissions": {
      "allow": [
        "Bash(cat **)"
      ]
    }
  }
  1. Test various commands:

| Command | Expected | Actual |
|------------------|--------------|--------------|
| cat 'hello' | Auto-allowed | Auto-allowed |
| cat 'bla () bla' | Auto-allowed | Auto-allowed |
| cat 'hello()' | Auto-allowed | Prompts |
| cat 'bla(x)' | Auto-allowed | Prompts |
| cat 'bla)' | Auto-allowed | Prompts |
| cat 'bla(' | Auto-allowed | Prompts |
| cat '(bla' | Auto-allowed | Prompts |
| cat ')bla' | Auto-allowed | Prompts |
| cat 'bla(bla' | Auto-allowed | Prompts |
| cat 'hello() ' | Auto-allowed | Auto-allowed |

  1. Any ( or ) in the command breaks matching — except when surrounded by spaces (e.g. bla () bla).
  2. Workaround: Append a trailing space to commands that contain parentheses:

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.72 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Xterm

Additional Information

_No response_

View original on GitHub ↗

7 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/21422
  2. https://github.com/anthropics/claude-code/issues/26630
  3. https://github.com/anthropics/claude-code/issues/27088

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

koby08 · 5 months ago

I think it may be a duplicate not sure, but this ticket is more basic and contains more cases

jake-albert · 5 months ago

Facing similar here for Next.js projects, closed my own issue https://github.com/anthropics/claude-code/issues/33827 to consolidate.

mfairley · 5 months ago

Hitting this as well on macOS. My specific case:

Allow rule: Bash(ls:*) in .claude/settings.local.json

Command that prompts despite the rule:

ls /Users/me/project/app/\(app\)/

The path contains Expo Router's (app) directory convention, which uses literal parentheses in the directory name. The shell-escaped \(app\) breaks the permission matcher.

Over time my settings have accumulated a bunch of overly-specific one-off entries like:

"Bash(ls -la /Users/me/project/app/\\\\\\(app\\\\\\)/in-person/)"

…because the general ls:* glob never matches these.

This is a common pattern in React Native / Expo projects where route groups use (group-name) directories, so it affects anyone working in those codebases.

Environment: Claude Code on macOS, latest version

bcherny collaborator · 4 months ago

Thanks for the report and the trailing-space workaround. This is fixed in v2.1.89.

Root cause in v2.1.72: the redirect-stripping step parsed and re-serialized the command, which dropped single quotes — cat 'hello()' became cat hello(). The now-unquoted ( ) then tripped the compound-command guard, which refused to match prefix/wildcard allow rules. Your trailing-space workaround happened to bypass the broken re-serialize step.

In v2.1.89 the redirect stripper preserves quoting, and all 7 cases from this issue return allow with Bash(cat:*). Please upgrade and reopen if you still see it.

koby08 · 4 months ago

Seems to work well now, thanks!

github-actions[bot] · 4 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.