[BUG] Bash permission glob matching fails when command contains curly braces with spaces combined with pipes

Resolved 💬 3 comments Opened Mar 16, 2026 by BrunoZirnstein Closed Mar 20, 2026

Preflight Checklist

  • [x] I have searched existing issues to make sure this isn't a duplicate
  • [x] This is a single bug report (not multiple bugs in one issue)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Bash permission glob patterns (e.g. Bash(pnpm test:execute-action *)) fail to match commands that contain curly braces { } with spaces inside them when the command also includes a pipe |. The glob matcher appears to misparse the curly braces as brace expansion patterns rather than treating them as literal characters in the command string.

This means any command passing JSON arguments with standard formatting (spaces after colons/commas) cannot be auto-allowed when piped.

What Should Happen?

The glob * (or **) in a permission pattern should match the entire command string literally, regardless of whether it contains characters that have special meaning in glob syntax ({, }, ,). The command string is not a pattern — it's a literal string being tested against the pattern.

Steps to Reproduce

Permission rule in settings.local.json:

{
  "permissions": {
    "allow": [
      "Bash(pnpm test:execute-action *)",
      "Bash(tail *)"
    ]
  }
}

Commands tested (all start with pnpm test:execute-action):

| Command suffix | Pipe | Auto-allowed? |
|---|---|---|
| --input '{"a":"b"}' -v | \| tail -5 | ✅ Yes |
| --input '{"a","b"}' -v | \| tail -5 | ✅ Yes |
| --input '{"a"}' -v | \| tail -5 | ✅ Yes |
| --input '{a b}' -v | \| tail -5 | ✅ Yes |
| -i x -a "y" | \| tail -5 | ✅ Yes |
| --input '{"a": "b"}' -v | \| tail -5 | ❌ Prompted |
| --input '{"a", "b"}' -v | \| tail -5 | ❌ Prompted |
| --input '{"a" "b"}' -v | \| tail -5 | ❌ Prompted |
| --input '{"a": "b"}' -v | (none) | ✅ Yes |
| --input '{"a" "b"}' -v | (none) | ✅ Yes |

Pattern: The match fails when ALL three conditions are met:

  1. Command contains { ... }
  2. Content inside braces has a space separating double-quoted strings (e.g. "a": "b", "a", "b", "a" "b")
  3. Command includes a pipe |

Remove any one of the three conditions and the match succeeds.

Root Cause Hypothesis

Curly braces are glob brace expansion characters ({a,b} matches a or b). When the glob matcher encounters content like {"key": "value"} with spaces inside the braces, it likely attempts brace expansion, fails or produces an unexpected expansion, and the overall match breaks. This only manifests when combined with a pipe — possibly because without the pipe, the * consumes the entire remaining string before the brace parser activates.

The command string should be treated as a literal — special glob characters in the string being matched (not the pattern) should not be interpreted.

Error Messages/Logs

No error — the command simply prompts for user confirmation instead of being auto-allowed by the permission rule.

Claude Model

Opus

Is this a regression?

I don't know

Claude Code Version

2.1.76

Platform

Claude Team subscription

Operating System

macOS

Terminal/Shell

Cursor (integrated terminal, zsh)

Additional Information

Changing the pattern to ** instead of * does not help — the issue is not about / matching (though * not matching / is a separate, known limitation). The ** variant has the same failure with braces + spaces + pipes.

Related issues (same class of bug — glob metacharacter interpretation of literal content):

  • #34379 — # character breaks glob matching
  • #27088 — '- sequence breaks glob matching
  • #27688 — Compound commands with pipes and quoted paths never match stored permissions
  • #13154 — Feature request for regex support in permission patterns

Practical impact: Any integration test command passing formatted JSON (with spaces after : or ,) cannot be piped to tail/head/grep without manual confirmation, even when both the base command and the pipe target are in the allow list.

View original on GitHub ↗

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