Claude Code Permissions: The settings.json I Actually Run (2026 Field Log)
The three arrays, the precedence rule nobody explains, the two gotchas that cost me an afternoon, and the exact settings.json allow, ask, and deny block I run now.

On this page
Quick Answer (2026): Claude Code permissions live in three arrays inside settings.json: allow, ask, and deny. Precedence is not top to bottom. deny always wins, then ask, then allow. Rules cascade across a file hierarchy (enterprise policy, then command-line flags, then project .claude/settings.local.json, then project .claude/settings.json, then your user ~/.claude/settings.json). The two gotchas that bite people: a Read deny rule also blocks the Edit tool on that file, and Bash() patterns match on the command prefix, so Bash(ls *) will not match ls -la. Type /permissions to see every active rule and which file it came from.
Yesterday Claude Code asked me to approve
git status for the fourth time in one session. That was the moment I stopped tolerating the permission prompts and actually read how the system works.
Turns out I had been holding it wrong for weeks.
This is my field log on Claude Code permissions in 2026: the three arrays, the precedence rule nobody explains clearly, the two gotchas that cost me an afternoon, and the exact settings.json I run now.
The three arrays that actually run the show
Claude Code permissions are three lists inside the permissions object of a settings.json file: allow, ask, and deny.
Each entry is a rule like Read, Edit, or Bash(git diff:*).
Here is the part that took me too long to internalize. The order is not top to bottom. It is by array:
denyis checked first and wins over everything.askis next. It pauses and prompts you.allowruns silently.
So an allow rule can never override a deny. If a command is stuck asking, adding it to allow in a lower file will not help if something upstream denies or asks for it. The official Claude Code permissions docs spell out the rule arrays, but the precedence is the bit I kept forgetting.
Gotcha one: a Read deny also kills Edit
I keep a hard deny on my env files:
"deny": ["Read(./.env)", "Read(./.env.*)"]
What I did not expect: a Read deny rule on a path also blocks the Edit tool on that same path. So Claude cannot read my .env, and it also cannot quietly rewrite it. That is exactly what I want for secrets, but it surprised me the first time an edit got refused with no read rule in sight.
If you only want to block reads and still allow edits, you have to think in tool pairs. For secrets, the double block is a feature. Write it down so future-you does not "fix" it.
Gotcha two: the glob is a prefix, not your shell
This one cost me an afternoon. I had:
"allow": ["Bash(ls *)"]
And Claude still asked me to approve ls -la.
Bash() patterns do not behave like your shell glob. They match on the command prefix. Bash(ls *) matches commands that start with ls followed by anything, but the matcher is picky about how the command string lines up, and small variants slip through. There is an open thread on exactly this: Bash allow patterns not matching.
The fix that worked for me is the documented colon-wildcard form: Bash(ls:*) for "ls with any arguments," and narrow prefixes like Bash(git diff:*) for scoped commands. When in doubt, run the command once, let it prompt, and add the exact rule from the /permissions UI so the pattern matches what Claude actually saw.
Where the rules live
Run /permissions inside Claude Code. It lists every active rule and, crucially, which file each one came from. That view is what finally made the hierarchy click for me.
From highest priority to lowest:
- Enterprise or managed policy (if your org sets one).
- Command-line flags for the current run.
- Project
.claude/settings.local.json(your personal, untracked rules). - Project
.claude/settings.json(checked into the repo, shared with the team). - User
~/.claude/settings.json(your global defaults).
I keep team-safe rules in the checked-in project file and my spicier personal allows in settings.local.json so I do not push my risk tolerance onto everyone else. The full field reference is in the Claude Code settings docs.
The settings.json I actually run
Here is my current base, trimmed to the parts that matter:
{
"permissions": {
"allow": [
"Read",
"Edit",
"Bash(git status)",
"Bash(git diff:*)",
"Bash(npm run test:*)",
"Bash(npm run build)"
],
"ask": [
"Bash(git push:*)",
"Bash(npm publish)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(rm -rf:*)",
"Bash(git reset --hard:*)"
]
}
}
The shape: read and edit are free, safe local commands are free, anything that touches a remote or a registry asks first, and anything that can nuke work or leak secrets is denied outright.
Three rules I deleted along the way:
- A blanket
Bash(*)in allow. It removed every prompt and also every guardrail. Never again. - A blanket
WebFetchallow. Too noisy, and I would rather see when it reaches out. Bash(git commit:*)sitting inask. Committing is cheap and reversible, so the prompt was pure friction. Moved it to allow.
I pair this with a PreToolUse hook for the couple of commands I want logged rather than blocked, which I wrote up in my Claude Code hooks field log. And if you are running subagents, remember they inherit this same permission set, so a loose allow leaks into every delegated task.
When I reach for bypass mode
There is a --dangerously-skip-permissions flag (bypass mode). It does what it says.
I use it in exactly one place: a throwaway container or a sandbox where nothing I care about lives and the whole thing gets deleted after. For anything touching my real machine, my repos, or my keys, a curated allow list plus a hard deny on secrets and destructive commands gets me almost the same speed without handing over the keys. If you want the deeper safety framing, I went into it in my Claude Code security notes.
If you are building agents from scratch and want the same permission discipline applied to a custom agent loop, the step-by-step tutorials over at AgentNotebook are a good next stop.
The takeaway
Spend fifteen minutes with /permissions, write a deny list for the four or five things that could actually ruin your day, allow the boring safe stuff, and let ask handle the middle. You stop fighting prompts and you never accidentally hand a coding agent rm -rf.
That trade has been worth it every single day since.
P.S. I still get one surprise prompt a week from a command variant my patterns miss. I have made peace with it. Better one stray prompt than one silent git reset --hard.
Written by
Dani ReyesIndie developer writing DevMoment from inside the work, on vibe coding, MCP, and weekend builds.
Frequently asked questions
Where is the Claude Code permissions file?
Claude Code reads permissions from settings.json files: .claude/settings.json in your project (shared with the team), .claude/settings.local.json for personal untracked rules, and ~/.claude/settings.json for global defaults. Run /permissions inside Claude Code to see which file each active rule comes from.
What is the order of precedence for allow, ask, and deny in Claude Code?
Precedence is by array, not top to bottom. deny is checked first and wins over everything, then ask, then allow. An allow rule can never override a deny rule, even one set in a lower-priority file.
Why is Claude Code still asking permission after I added an allow rule?
Usually a pattern-match miss. Bash() rules match on the command prefix, so Bash(ls *) does not cover ls -la. Use the documented colon-wildcard form like Bash(ls:*), or let the command prompt once and add the exact rule from the /permissions UI so the pattern matches what Claude actually ran.
Does a Read deny rule also block editing in Claude Code?
Yes. A Read deny rule on a path also blocks the Edit tool on that same path. That is why a single deny on Read(./.env) is enough to stop a coding agent from both reading and rewriting your secrets.
Should I use --dangerously-skip-permissions?
Only in a sandbox or throwaway container where nothing you care about lives. For day-to-day work on real repos and machines, a curated allow list plus a hard deny on secrets and destructive commands gives you nearly the same speed without removing the guardrails.
Keep reading
Claude Code hooks: the 4 I kept, the 2 I deleted (2026)
I ran Claude Code hooks daily for a month. Four earned their place, two got deleted, and one quietly made my workflow worse. The honest field log.
Claude Code Security in 2026: What GhostApproval Changed
GhostApproval showed my Claude Code approval box a fake filename while the write went to my SSH keys. Anthropic called it outside its threat model, so the fix was mine. Here is the field log: what the symlink flaw does, who patched, and the three setup changes that held.
Claude Code Subagents: The 2 I Actually Kept (2026 Field Log)
Two weeks of pushing real work onto Claude Code subagents. The two I kept, the frontmatter line that actually decides delegation, and the gotcha that cost me an afternoon.


