Read-only Bash commands prompt for paths outside project even when Read tool is globally allowed
Bug Description
Read-only Bash commands (e.g., grep, cat, head) targeting files outside the project directory trigger a permission prompt, even when:
- The user has globally allowed the command pattern (e.g.,
Bash(grep *)in~/.claude/settings.json) - The user has globally allowed the
Readtool (i.e.,"Read"inpermissions.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
- Add
Bash(grep *)and"Read"to~/.claude/settings.jsonunderpermissions.allow - Open a project (e.g.,
/home/user/myproject/) - 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
- Claude prompts for permission despite both
Bash(grep *)andReadbeing 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 beforecheckPathConstraints(), so verified read-only commands on external paths are auto-allowed - Have
checkPathConstraints()consult the user'sRead(path)rules and globalReadpermission 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"inpermissions.allow
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗