GrepBoard/Manuals
51 manuals ·

Claude Code settings.local.json allow rule not applied

·Claude Code 2.1.302 · Ubuntu 24.04 · run 2026-10-19·8 steps

Claude Code still asks despite a Bash allow rule in settings.local.json. Check loaded files, rule syntax, compound commands and deny precedence via claude -p.

You added Bash(touch:*) to .claude/settings.local.json, yet Claude Code still asks before every touch. Work through the file first, then the rule.

[1] Run /status in the session and look for Project local settings on the Setting sources line. The docs say that line lists the settings files this session loaded. If it is missing, the file was never read, whatever the rule says.

[2] Check the location and the JSON. The project-local file is .claude/settings.local.json in the project. Started from a subdirectory of a git repository, Claude Code reads the file at the repository root, per the docs.

.claude/settings.local.json
{  "permissions": {  "allow": [    "Bash(touch:*)"  ]  }}

[3] Check whether git tracks the file. The docs say that when settings.local.json is tracked, or .claude is a symlink, Claude Code treats it as supplied by the repository and holds its allow rules until you trust the folder. Add a hand-made file to .gitignore. A -p run counts as trusted, so this only shows up in interactive sessions.

[4] Run the rule. Read-only commands such as git status run without asking even with no rule, so they prove nothing. This test uses touch. Rejected calls are listed in permission_denials in the JSON output.

console
$ cat .claude/settings.local.json{"permissions":{"allow":["Bash(touch:*)"]}}$ claude -p "Run exactly this shell command and nothing else, then reply with only OK or the reason it did not run: touch a.txt" --max-turns 3 --output-format json > r1.json$ jq -c '.permission_denials' r1.json[]$ ls *.txta.txt

a.txt exists and nothing was denied, so on this version the allow rule in settings.local.json works. GitHub issue #32838 reports Bash(rm:*) in the same file showing up in /permissions while the prompt still appears. It was closed as a duplicate with no maintainer reply and no confirmed cause. If you see the same thing, rule out [5] to [7] first.

[5] :* is a wildcard only at the very end of a rule. The docs say that in Bash(git:* push) the colon is a literal character and matches no git command.

console
$ cat .claude/settings.local.json{"permissions":{"allow":["Bash(touch:* d.txt)"]}}$ claude -p "Run exactly this shell command and nothing else, then reply with only OK or the reason it did not run: touch d.txt" --max-turns 3 --output-format json > r3.json$ jq -c '.permission_denials[].tool_input.command' r3.json"touch d.txt"

Change it to Bash(touch d.txt) or Bash(touch *). At the end, :* and * mean the same. The space is part of the rule: Bash(ls *) does not match lsof, Bash(ls*) does.

[6] A command joined with &&, ||, ; or | needs every part allowed on its own. Send a command with an rm tacked on while only Bash(touch:*) is allowed.

console
$ cat .claude/settings.local.json{"permissions":{"allow":["Bash(touch:*)"]}}$ claude -p "Run exactly this shell command and nothing else, then reply with only OK or the reason it did not run: touch c.txt && rm -f a.txt" --max-turns 3 --output-format json > r2.json$ jq -c '.permission_denials[].tool_input.command' r2.json"touch c.txt && rm -f a.txt"$ ls *.txta.txt

c.txt was not created and a.txt is still there. The allowed touch half did not run either; the whole command was blocked. The prompt came from rm, not touch. Add an rm rule or run the commands separately.

[7] Look for a deny rule on the same command. Rules are evaluated deny, then ask, then allow, and the first match decides. The docs say a deny in any settings file beats an allow in another. Put the allow in the local file and the deny in the shared settings.json:

console
$ cat .claude/settings.local.json .claude/settings.json{"permissions":{"allow":["Bash(touch:*)"]}}{"permissions":{"deny":["Bash(touch e.txt)"]}}$ claude -p "Run exactly this shell command and nothing else, then reply with only OK or the reason it did not run: touch e.txt" --max-turns 3 --output-format json > r4.json$ jq -c '.permission_denials[].tool_input.command' r4.json"touch e.txt"

Adding allow rules to the local file does not override a deny elsewhere. For the deny side, see Block .env reads in Claude Code with a deny rule.

[8] Whether a restart is needed after editing follows the docs: Claude Code watches the settings files and applies permissions edits to the running session. A file created mid-session is loaded if its folder already existed when the session started. If the change still does not show, restart the session and check [1] again with /status. List keys such as permissions.allow are merged across files, not overwritten.

#claude-code#permissions#settings