Bash(command:*) allow rules do not prevent permission prompts for file-modifying commands
Description
Bash commands explicitly listed in the allow array of .claude/settings.json do not prevent permission prompts - commands still prompt for permission despite being explicitly allowed.
Environment
- Platform: Linux (Synology NAS)
- OS Version: Linux 6.12.30+
- Claude Code Version: Latest (January 2026)
Configuration
Project-level .claude/settings.json with 200+ explicit allow rules:
{
"permissions": {
"allow": [
"Bash(mkdir:*)",
"Bash(touch:*)",
"Bash(mv:*)",
"Bash(cp:*)",
"Bash(curl:*)",
"Bash(tar:*)",
"Bash(echo:*)",
"Bash(tee:*)",
"Bash(sed:*)",
"Bash(ln:*)",
"Bash(chmod:*)"
// ... 200+ other rules
],
"ask": ["Bash(rm:*)"],
"deny": ["Bash(rm -rf:*)", "Bash(sudo:*)", "Bash(wget:*)"]
}
}
Steps to Reproduce
- Create
.claude/settings.jsonwith commands in allow array (full config available) - Restart Claude Code session
- Run:
mkdir test-dir - Run:
touch test-dir/file.txt - Run:
curl -sL https://example.com -o /tmp/test.tar.gz - Run:
tar -xzf /tmp/test.tar.gz
Test Results (2026-01-23)
Commands that PROMPTED despite being in allow list:
| Command | In Allow List | Result |
|---------|---------------|--------|
| mkdir perm-test | Bash(mkdir:*) | PROMPTED |
| touch test1.txt | Bash(touch:*) | PROMPTED |
| echo "test" > file.txt | Bash(echo:*) | PROMPTED |
| tee file.txt <<< "test" | Bash(tee:*) | PROMPTED |
| cp file1 file2 | Bash(cp:*) | PROMPTED |
| mv file1 file2 | Bash(mv:*) | PROMPTED |
| curl -sL url -o file | Bash(curl:*) | PROMPTED |
| tar -xzf archive.tar.gz | Bash(tar:*) | PROMPTED |
Commands that worked WITHOUT prompting:
| Category | Commands |
|----------|----------|
| Read-only system | ls, pwd, whoami, hostname, date, uname, df, ps, env |
| Read-only file | cat, head, tail, wc, grep, find, file, stat, md5sum |
| Version/help | git --version, node --version, curl --version, tar --version |
| Git operations | git status, git log, git branch, git diff, git remote |
Deny rules that worked correctly:
| Command | Result |
|---------|--------|
| rm -r dir | BLOCKED ✓ |
| rm -rf dir | BLOCKED ✓ |
| sudo ls | BLOCKED ✓ |
| wget url | BLOCKED ✓ |
Key Observations
- Allow rules don't work for file-modifying commands: mkdir, touch, cp, mv, echo redirect, tee, sed -i all prompt
- Allow rules don't work for network commands: curl with -o flag prompts
- Allow rules don't work for archive commands: tar extraction prompts
- Deny rules work correctly: Blocked commands are consistently blocked
- Read-only commands work: Never prompt (may not even check allow rules)
Conditions Verified
- [x] Correct syntax
Bash(command:*)per documentation - [x] Session restarted after config changes
- [x] No
settings.local.jsonoverwriting permissions - [x] Valid JSON confirmed with
jq . - [x] File in correct location (
.claude/settings.jsonin project root) - [x] Commands NOT in deny or ask arrays
Hypothesis
It appears that:
- Deny rules are checked and enforced
- Read-only commands may bypass permission checks entirely
- Allow rules for file-modifying/network commands are either not checked or not matched correctly
Related Issues
- #17017 - Project-level permissions replace global instead of merging
- #6900 - settings.local.json overwrites existing manual settings
- #13340 - Global allow rules not recognized
Expected Behavior
Commands listed in the allow array with Bash(command:*) syntax should execute without prompting for permission.
Actual Behavior
File-modifying and network commands prompt for permission regardless of allow rules. Only deny rules and read-only commands behave as expected.
7 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Additional clarification: This issue affects both project-level (
.claude/settings.json) AND global-level (~/.claude/settings.json) configurations.During testing, we verified:
~/.claude/settings.jsonwith the same allow rules - same behavior (prompts for file-modifying commands).claude/settings.json- same behaviorThe allow rules are not honored at either configuration level for file-modifying and network commands.
Hitting this same issue with
mkdirbeing blocked in directories that Claude is already authorized in. Thank you for filing.Systematic permission testing confirms this bug — also documents the reverse: commands auto-approving WITHOUT allow rules.
Environment: Claude Code v2.1.34, VS Code Extension, Windows 10,
defaultMode: "acceptEdits"Setup:
.claude/settings.local.jsonwith explicit allow rules. Tested 50 commands in a single session, user manually flagged each prompt.Commands that PROMPT despite being in the allow list
| Rule in
settings.local.json| Command Tested | Result ||------|---------|--------|
|
Bash(cd *)|cd /tmp| Prompted ||
Bash(cp *)|cp --help| Prompted ||
Bash(cp *)|cp --version| Prompted ||
Bash(mkdir *)|mkdir -p /tmp/test| Prompted |All four are "file-modifying" or "directory-changing" commands. The allow rules are correctly formatted (confirmed other rules with same syntax work fine).
Commands that AUTO-APPROVE despite NO allow rule anywhere
| Allow rule? | Command Tested | Result |
|-------------|---------|--------|
| None |
rm --help| Auto-approved || None |
rm --version| Auto-approved || None |
python --version| Auto-approved |These commands are not in
settings.local.json,~/.claude/settings.json, or~/.claude.jsonprojectallowedTools. They appear to be auto-approved by an internal classification system.Control group — rules that DO work correctly
| Rule | Command | Result |
|------|---------|--------|
|
Bash(ls *)|ls -la ...| Auto-approved ||
Bash(git commit *)|git commit -m "msg"| Auto-approved ||
Bash(git push *)|git push origin branch| Auto-approved ||
Bash(pnpm *)|pnpm build| Auto-approved || Not in allow list |
curl --version| Prompted || Not in allow list |
gh issue list| Prompted |Summary
The permission system appears to have two separate issues:
cd,cp,mkdir)rm --help,python --version)The user-facing settings give the impression of full control, but the internal classification overrides them in both directions.
One common cause of this: Claude adds
# commentlines before the command, which changes the string the permission matcher sees. A PreToolUse hook can strip those comments so the matcher works:Includes a comment-strip hook that removes leading
#lines from bash commands before permission matching. Won't fix all cases (e.g.,$(...)substitution also breaks matching — see #31373), but handles the comment case.Source: comment-strip.sh
Still reproducing on Claude Code desktop app Claude 1.7196.0 (2dbd78) (macOS).
Repro
A bare, single
git add <file>command — no chaining, no redirects, no variables, no substitution — prompts for approval despite multiple overlapping allowlist patterns.Settings (
~/.claude/settings.json)Command that prompts
Observed
Permission prompt appears with only "Deny" and "Allow once" — no "Always allow" option (related: #36514).
[attach screenshot here]
Expected
Command matches at least four allowlist patterns and should execute without prompting.
What I've ruled out
&&,;,|, redirects)settings.jsonoverriding the user-global allowlistdenyoraskrules anywhere/permissionsthat the rules above are loadedRelated open issues suggesting the underlying matching logic still has gaps
Bash(*)wildcard doesn't match all Bash commandsEscape hatches that don't work
defaultMode: bypassPermissionsin settings.json — desktop app ignores it (#42975, #38148, #29026)--dangerously-skip-permissions— CLI flag, not exposed by desktop appgit add(#36514)So the user-facing impact is: every commit costs two clicks in the desktop app (
git add, thengit commit), and there's no config or UI workaround. Considering reopening this or filing a new issue — happy to do whichever the maintainers prefer.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.