Read() deny rules force manual approval on unrelated Bash grep commands after cd with relative glob

Status Open
Reported on v2.1.259
Maintainer reply None cached
Activity 3 comments · opened Sep 3, 2026

Description

When a Read() deny rule is configured in settings.json (e.g. to protect .env/.npmrc files), Claude Code prompts for manual approval on any Bash command that does cd <dir> && grep ... <relative-glob>, even when the grep target obviously cannot match the denied pattern.

Example

Settings:

"deny": [
  "Edit(*.env*)",
  "Read(*.env)",
  "Edit(*.npmrc*)",
  "Read(*.npmrc)"
]

Command that triggers an unexpected prompt:

cd /Users/me/project/docs
grep -rniI "pattern" open_questions/*.md research/*.md 2>/dev/null | grep -i foo

The tool-permission UI explains: "grep on 'open_questions/.md' after a cd would search a directory that cannot be determined here, and a Read() deny rule is configured; only you can approve running it anyway."*

Expected behavior

Since the glob (open_questions/*.md, research/*.md) cannot possibly resolve to *.env or *.npmrc, this command should run without a prompt — the deny rule is unrelated to the target files.

Actual behavior

Any Read() deny rule anywhere in settings, combined with a cd preceding a relative-path glob in a Bash file-reading command (grep/cat/head/tail/sed), forces a manual approval prompt, regardless of whether the glob could ever match a denied path. There is also no way to write an allow rule (e.g. Bash(grep *)) that is guaranteed to suppress just this class of prompt while keeping deny rules in effect — behavior seems to require accepting the prompt or restructuring commands to use absolute paths.

Environment

  • Claude Code version: 2.1.259
  • OS: macOS 26.3 (BuildVersion 25D125)

Impact

This makes it impractical to keep narrow, useful Read() deny rules (protecting secrets like .env/.npmrc) without incurring frequent unrelated permission prompts on ordinary read-only search commands run via cd + relative globs, including in automated background workflows where no one is present to approve.

View original on GitHub ↗

3 Comments

Sediug · 2 days ago

Same regression here on Linux + VS Code extension 2.1.259, permission mode auto. Adding a few data points that narrow it down.

Environment

  • Ubuntu 24.04.4 LTS (kernel 6.17), VS Code 1.132.0, Claude Code extension auto-updated 2.1.258 → 2.1.259 on 2026-09-03 12:19 CEST. All sessions on 2.1.258 the day before: 0 prompts. First afternoon on 2.1.259: 10+ prompts in a couple of minutes, all on compound read-only commands that include a grep call (cd DIR; grep ... | head, grep ... | sed -n, grep -r --include ..., pgrep ... | grep -v, ... | grep -i ... | head); the same shapes never prompted on 2.1.258.
  • ~/.claude/settings.json: defaultMode: "auto", deny: ["Read(.env)", "Read(.env.production)", "Read(.env.staging)", "Edit(.env)", ...]. No ask rules, no hooks, no managed settings.

What the logs show
The prompted calls never reach the auto-mode classifier: the extension log (Claude VSCode.log) has no [auto-mode] new action being classified entry for them, only executePermissionRequestHooks called for tool: Bash followed by the dialog, and later Slow permission decision: 87515ms for Bash (mode=auto, behavior=allow) when the user clicks. Commands with an explicit subdirectory and no cd are auto-allowed in ~10 ms, so the gate is in the static Bash path check, not in the classifier.

Two triggers, both gated on "any Read(...) deny rule exists"

  1. cd DIR followed by a relative path argument to grep/egrep/fgrep/rg/diff/git/cp/mv"<cmd> on '<path>' after a cd would search a directory that cannot be determined here, and a Read() deny rule is configured; only you can approve running it anyway". The cd is only consolidated when the whole command is cd DIR && ... (no ;, no ||, no newline, no bare &). With cd /abs/path; grep -n x rel/file.php | head the target is a literal absolute path inside the working directory and it still prompts. Your example (cd on one line, grep on the next) is the newline case.
  2. Without cd: grep -r ... . or grep -r with no path (implicit .) from the directory that holds .env"<cmd> on '.' would read '.env', which the deny rule Read(.env) covers; only you can approve running it anyway". That is the #91778 / #91795 case.

The result is behavior: "ask" with classifierApprovable: false, which is why auto mode cannot rescue it. Neither message string exists in the 2.1.258 binary (0 hits); both are present in 2.1.259.

Examples that prompted (repo root as cwd, all read-only)

cd /home/me/project; grep -n "'Order'" src/models/order.php | head -30
cd /home/me/project; grep -rn "'Order'\s*=>" src/models/ --include=*.php | head -20
grep -rn "function recalculateTotals" --include=*.php | head -20

grep -rn "token" --include=*.php src lib tests (no cd, explicit dirs) was auto-allowed.

Workaround Pinned the VS Code extension to 2.1.258; removing the Read(.env*) deny rules also makes the prompts disappear but drops the protection, so not an option.

JPBM135 · 2 days ago

Same regression on MacOs 27. I'm currently reverting to 258, this is the command if anyone wants it:

claude install 2.1.258
jander99 · 2 days ago

Apologies for being unable to post with my enterprise managed user account, which means I can't share any diagnostic data, but consider this a "me too" comment. I typically run with bypassPermissions as the default because of the amount of subagents.

My workflow is typically at a directory level above all my checked out repos. I have ~30 microservices (Java, Spring Boot), 2 UIs (one nx monorepo and typescript and one javascript), and several operations-type repos (actions, cdk8s, terraform). Working at a directory above means that the model (Sonnet 5 mostly) will chain a "cd ..." into one of the sub directories to perform whatever function it needs, such as grep.

Sonnet itself suggested I install a custom hook since hooks are processed before deny() and read() functions. I don't agree with that. My assumption here would be that if I am stating I want to bypass permissions, that bypass should include the new rule that was put in as part of 2.1.259. Hopefully that was the overall intent.

Cheers!