Read-only Bash commands prompt for paths outside project even when Read tool is globally allowed

Resolved 💬 3 comments Opened May 13, 2026 by xshuo Closed May 16, 2026

Bug Description

Read-only Bash commands (e.g., grep, cat, head) targeting files outside the project directory trigger a permission prompt, even when:

  1. The user has globally allowed the command pattern (e.g., Bash(grep *) in ~/.claude/settings.json)
  2. The user has globally allowed the Read tool (i.e., "Read" in permissions.allow)

The same file can be read silently by Claude's Read tool, but grep on the same file triggers a confirmation prompt. This is an inconsistency — both operations have identical security impact (read-only access to the same file).

Steps to Reproduce

  1. Add Bash(grep *) and "Read" to ~/.claude/settings.json under permissions.allow
  2. Open a project (e.g., /home/user/myproject/)
  3. Ask Claude to grep a file outside the project directory, such as a Go module dependency:

``
grep -n "type Tool struct" ~/go/pkg/mod/github.com/some-lib@v1.0.0/types.go
``

  1. Claude prompts for permission despite both Bash(grep *) and Read being globally allowed

Meanwhile, using Claude's Read tool on the same file passes silently.

Root Cause

In bashToolCheckPermission() (bashPermissions.ts), the execution order is:

Step 1: Exact match check
Step 2: Prefix deny/ask rules
Step 3: checkPathConstraints()   ← returns 'ask' for external paths, SHORT-CIRCUITS here
Step 4: Allow exact match        ← never reached
Step 5: Allow prefix/wildcard    ← never reached (Bash(grep *) lives here)
Step 7: isReadOnly() check       ← never reached (read-only auto-allow lives here)

checkPathConstraints() at step 3 returns 'ask' for any path outside the project directory, short-circuiting the pipeline before the allow rules (step 5) and the read-only auto-allow check (step 7) are ever evaluated.

Expected Behavior

If a Bash command is verified as read-only (grep, cat, head, etc.) and the user has globally allowed the Read tool or the specific command pattern, checkPathConstraints() should not block it. The read-only determination (step 7) or allow-rule matching (step 5) should take precedence over the coarse path constraint check.

Possible approaches:

  • Move isReadOnly() check before checkPathConstraints(), so verified read-only commands on external paths are auto-allowed
  • Have checkPathConstraints() consult the user's Read(path) rules and global Read permission before returning 'ask'
  • Check allow rules (step 5) before path constraints (step 3)

Impact

This affects any workflow that reads dependency source code outside the project directory:

  • Go module cache (~/go/pkg/mod/)
  • Rust crates (~/.cargo/registry/)
  • System headers (/usr/include/, /usr/local/include/)
  • Xcode SDKs (/Library/Developer/CommandLineTools/)

Workaround

Add explicit Read() path rules for each external directory:

"Read(/Users/me/go/pkg/mod/**)",
"Read(/usr/include/**)"

This works but is verbose, platform-specific, and conceptually redundant when Read is already globally allowed.

Environment

  • Claude Code CLI
  • macOS / Linux
  • Global settings with Bash(grep *) and "Read" in permissions.allow

View original on GitHub ↗

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