[BUG] Bash security heuristic false-positives on Next.js dynamic route brackets [param] — blocks write commands despite allow rules

Resolved 💬 2 comments Opened Jun 8, 2026 by RikuShimoida Closed Jul 14, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

The Bash security heuristic layer treats [param] in file paths as glob patterns and blocks all Bash write commands targeting those paths with:

"Glob patterns are not allowed in write operations. Please specify an exact file path."

This is a critical false positive for Next.js App Router projects, where [param] dynamic route directories (e.g., [barId], [articleId], [userId]) are fundamental to the framework's file-based routing.

The block cannot be bypassed via settings.json allow rules, PreToolUse Hooks, or any quoting/escaping of the path — it operates on a separate heuristic layer that inspects the raw command string.

Systematic Test Results (50 patterns tested)

We conducted a comprehensive 50-pattern test to isolate the exact triggering conditions.

Trigger condition: Bash write command + glob characters in path

| # | Command | Glob char | Write? | Prompted? | Message |
|---|---------|-----------|--------|-----------|---------|
| 1 | touch .../app/test-plain.tmp | None | Yes | No | — |
| 2 | touch .../app/test-{a,b}.tmp | { } | Yes | Yes | Glob patterns... |
| 3 | touch .../app/test-star*.tmp | * | Yes | Yes | Glob patterns... |
| 4 | touch .../app/test-question?.tmp | ? | Yes | Yes | Glob patterns... |
| 5 | touch .../app/test-tilde~.tmp | ~ | Yes | No | — |
| 6 | touch .../app/test-dollar$var.tmp | $ | Yes | Yes | Shell expansion syntax... |
| 7 | touch .../app/test-backtick\.tmp | ` | Yes | **No** | — |
| 8 |
touch .../app/test-paren(1).tmp | ( ) | Yes | **No** | — |
| 9 |
touch .../app/test-space name.tmp | space | Yes | **No** | — |
| 10 |
touch .../app/test-excl!.tmp | ! | Yes | **No** | — |
| 11 |
touch .../app/test-hash#.tmp | # | Yes | **No** | — |
| 12 |
touch .../app/test-at@.tmp | @` | Yes | No | — |

Read vs Write with [barId]

| # | Command | Read/Write | Prompted? |
|---|---------|-----------|-----------|
| A | ls .../bars/[barId] | Read | No |
| F | cat .../bars/[barId]/route.ts | Read | No |
| B | touch .../bars/[barId]/file.tmp | Write | Yes |
| E | cp .../bars/[barId]/a .../bars/[barId]/b | Write | Yes |
| 50 | chmod 644 .../bars/[barId]/file.tmp | Write | Yes |
| 15 | echo "test" > .../bars/[barId]/file.tmp | Write | Yes |
| 47 | echo "test" >> .../bars/[barId]/file.tmp | Write | Yes |

Tools that are NOT affected (same [barId] path)

| # | Tool/Method | Prompted? |
|---|------------|-----------|
| D | Write tool | No |
| 48 | Edit tool | No |
| 37 | node -e "fs.writeFileSync('.../[barId]/...', ...)" | No |
| 26 | cat <<'EOF' > .../[barId]/file.tmp (heredoc) | No |
| 40 | mkdir .../bars/%5BbarId%5D/... (URL-encoded) | No |

Shell expansion variants

| # | Command | Prompted? | Message |
|---|---------|-----------|---------|
| 13 | touch .../test-$(echo hi).tmp | Yes | Shell expansion syntax... |
| 16 | touch ~/Documents/.../file.tmp | No | — |
| 17 | touch $HOME/Documents/.../file.tmp | Yes | Contains simple_expansion |

Structural commands

| # | Command | Prompted? | Message |
|---|---------|-----------|---------|
| 30 | if [ -d ... ]; then echo "exists"; fi | No | — |
| 31 | for f in .../test-*.tmp; do echo "$f"; done | Yes | Unhandled node type: string |
| 32 | for f in a b c; do echo "$f"; done | Yes | Unhandled node type: string |
| 33 | while false; do echo "x"; done | No | — |

Key Findings

  1. The heuristic inspects the raw command string for glob characters ([, ], *, ?, {, }) and shell expansion ($), then blocks if the command is classified as a "write operation"
  2. settings.json allow rules have no effectBash(mkdir:*), Bash(touch:*), Bash(cp:*) are all in the allow list but the heuristic fires on a separate layer
  3. Quoting does not help — single quotes, double quotes, backslash escaping all still trigger the prompt
  4. Variable indirection does not helpDIR=".../[barId]" && mkdir "$DIR" still triggers
  5. Write/Edit tools are unaffected — the heuristic only applies to Bash tool commands
  6. for loops always trigger regardless of content ("Unhandled node type: string")

Impact on Next.js Development

Next.js App Router uses [param] directories as a core routing convention:

app/
├── bars/
│   └── [barId]/          ← Every Bash write command here triggers a prompt
│       ├── route.ts
│       ├── menus/
│       │   └── [menuId]/ ← Also triggers
│       ├── articles/
│       │   └── [articleId]/ ← Also triggers
│       └── events/
│           └── [eventId]/   ← Also triggers

In a typical Next.js project, mkdir -p, touch, cp, echo > into these directories happen dozens of times per session. Each one stalls the session waiting for manual approval.

What Should Happen?

  1. [param] in file paths should not be treated as glob patterns when the path is quoted or escaped
  2. settings.json allow rules should take precedence over the heuristic layer
  3. At minimum, the heuristic should check whether the bracketed content actually looks like a glob range ([a-z], [0-9]) vs. a named parameter ([barId], [userId])

Workarounds (all unsatisfactory)

  • --dangerously-skip-permissions — disables all safety, too broad
  • "Yes, and always allow access to [barId]/" — must be done per-directory, new [param] dirs trigger again
  • Use Write/Edit tools instead of Bash — not always possible (mkdir, cp, chmod)
  • URL-encode brackets as %5BbarId%5D — creates wrong directory names

Related Issues

  • #36341 (closed as duplicate)
  • #32212 (closed as stale — not fixed)
  • #34106
  • #35183

Error Messages/Logs

Glob patterns are not allowed in write operations. Please specify an exact file path.
Shell expansion syntax in paths requires manual approval
Contains simple_expansion
Unhandled node type: string
This command requires approval

Steps to Reproduce

  1. Create a Next.js App Router project with [param] directories
  2. Add Bash(mkdir:*), Bash(touch:*), Bash(cp:*) to settings.json allow list
  3. Run any Bash write command targeting a [param] path:

``bash
mkdir -p ./app/api/bars/\[barId\]/portal
touch ./app/api/bars/\[barId\]/route.ts
cp ./source.ts ./app/api/bars/\[barId\]/dest.ts
``

  1. Observe: permission prompt appears despite allow rules

Claude Model

Opus

Is this a regression?

No, this has been present since at least v2.1.71

Claude Code Version

2.1.98 (Claude Code)

Platform

Anthropic API

Operating System

macOS 26.3.1

Terminal/Shell

VS Code integrated terminal (zsh)

Additional Information

This was tested with a comprehensive 50-pattern test matrix. The project is a Next.js monorepo with both user-facing and admin apps, both using App Router's [param] convention extensively.

View original on GitHub ↗

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