[BUG] Bash permission glob pattern fails to match paths containing parentheses (Next.js route groups)

Resolved 💬 3 comments Opened Mar 23, 2026 by Artforge Closed Mar 27, 2026

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?

The Bash(cat:*) permission pattern fails to match cat commands when the file path contains parentheses, such as Next.js App Router route groups like (private) and (public). Despite selecting "Yes, and don't ask again for: cat:*" multiple times, the user is re-prompted every time.

The permission IS correctly saved to settings.local.json and settings.json — the issue is that the glob matcher doesn't match the command at runtime.

What Should Happen?

Bash(cat:*) should match cat "/path/to/app/(private)/layout.tsx" since * should match any character sequence including parentheses.

Steps to Reproduce

  1. Have a Next.js App Router project with route groups (directories named (private), (public), etc.)
  2. Have Bash(cat:*) in your permissions allow list (both ~/.claude/settings.json and .claude/settings.local.json)
  3. Claude Code (or an MCP tool) runs: cat "/path/to/project/apps/web/src/app/(private)/layout.tsx"
  4. Expected: Command auto-allowed (matches cat:*)
  5. Actual: User prompted to approve, with options including "Yes, and don't ask again for: cat:*" — which they've already selected before

Why This Matters

Next.js App Router uses parenthesized directory names as a core feature (route groups). Common patterns:

  • app/(private)/layout.tsx — authenticated routes
  • app/(public)/layout.tsx — public routes
  • app/(auth)/login/page.tsx — auth-related routes
  • app/(marketing)/page.tsx — marketing pages

This means every Next.js App Router project with route groups will hit this bug for any Bash command that touches files in those directories.

Permission State (verified)

~/.claude/settings.json:

{
  "permissions": {
    "allow": [
      "Bash(cat:*)"
    ]
  }
}

<project>/.claude/settings.local.json:

{
  "permissions": {
    "allow": [
      "Bash(cat:*)"
    ]
  }
}

Both files contain the pattern. The pattern is saved but never matches.

Prompt Shown to User

Bash command
  cat "/Users/.../apps/web/src/app/(private)/layout.tsx"
  Read private layout

This command requires approval

Do you want to proceed?
❯ 1. Yes
  2. Yes, and don't ask again for: cat:*
  3. No

Error Messages/Logs

No error — the permission just silently fails to match.

Related Issues

  • #14522 — Parentheses in PowerShell commands inside Bash() pattern (parser-level failure, different root cause)
  • #26630 — Special characters in quoted paths (Windows, closed as dup of #15292)
  • #15292 — Env var prefixes not matching (closed/stale)
  • #34379 — # character breaks matching

These are all closed and cover different specific cases. This issue is specifically about parentheses in file paths (not in the command syntax), which is especially impactful for the Next.js ecosystem.

Claude Model

Opus

Is this a regression?

No, this never worked

Claude Code Version

Latest (March 2026)

Platform

Anthropic API

Operating System

macOS (Darwin 24.6.0)

Terminal/Shell

zsh

Additional Information

Workaround: Use the dedicated Read tool instead of cat via Bash — this bypasses the Bash permission system entirely. However, MCP tools and subagents may still invoke cat, causing the prompt to appear.

Suggested fix: The glob matcher for Bash() permission patterns should treat * as matching any character (including parentheses, brackets, etc.) — or use simple string prefix matching rather than glob matching for the :* suffix pattern.

View original on GitHub ↗

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