Read() deny rule on a root file makes every `grep -r .` prompt under bypassPermissions (2.1.257 reader-command rule, untouched by the 2.1.260 revert)
Summary
A Read() deny rule on a file at the project root makes every recursive Bash command whose path argument is the project root require manual approval — including grep -r <pattern> . — even with defaultMode: "bypassPermissions".
The trigger is an ancestor-directory match: the argument is ., the deny rule is Read(./.env), and . contains ./.env. Naming any subdirectory instead runs clean.
This is the same over-match class that 2.1.260 just reverted for the 2.1.259 change (npm run build denied under Read(./**/build/**)), but it arrives through the 2.1.257 reader-command rule, which the revert did not touch.
Environment
- Claude Code 2.1.260 (auto-updated from 2.1.259 the same day)
- Windows 11, Git Bash (
CLAUDE_CODE_GIT_BASH_PATHset) permissions.defaultMode: "bypassPermissions"in user settings (~/.claude/settings.json), so the 2.1.257 project-settings restriction does not apply- No managed settings present
Reproduce
- In user settings:
"permissions": {
"defaultMode": "bypassPermissions",
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
}
- Create a
.envat the project root. - Run each of the following through the Bash tool.
Observed
| Command | Result |
| --- | --- |
| grep -rn "<pattern>" <explicit/file/path> | runs clean |
| grep -rl "<pattern>" src/ | runs clean |
| grep -rl "<pattern>" . | prompts for approval |
| ls -la .env* secrets | denied outright — Permission to use Bash with command ... has been denied |
| Grep tool, path = project root | runs clean |
The exact-match case (ls .env*) hard-denying is correct and expected. The containment case (.) prompting is the problem: it fires on every root-level recursive read, and the vast majority of those have no interest in the denied file.
Expected
A directory argument that merely contains a denied path should not force approval of the whole command. The Grep tool already models the better behaviour — it applies deny rules per file and skips them, rather than refusing the call.
Why this is costly
bypassPermissions is chosen precisely to avoid interactive approval. A rule that reintroduces a prompt on one of the most common commands, in a mode whose whole purpose is unattended operation, is very hard to attribute: from the user's side the mode indicator reads "bypass", the settings look correct, and nothing names the deny rule as the cause. It took a changelog read to find it after an audit of settings, managed settings, hooks, and sandbox config turned up nothing.
Suggested fix
Either:
- Restrict the reader-command check to arguments that match a deny rule as a path (exact or glob), not as an ancestor directory; or
- Let the command run and filter denied files the way Glob/Grep already do.
Relevant changelog entries
- 2.1.257 — "Fixed Bash
Read()/Edit()deny rules not applying to< fileredirects and reader commands liketacandegrep; a deny rule on any argument or redirect target now refuses the command" - 2.1.259 — "Fixed Bash
Read()deny rules not covering files given as option values (--ignore-revs-file=.env,-f.env,@file),git diff/git grepfile operands, orcd DIR && cat FILEcompounds" - 2.1.260 — "Reverted the 2.1.259 change applying
Read()deny rules to Bash arguments; it deniednpm run buildunder aRead(./**/build/**)rule in every mode"
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗