AI dev workflow
Dani Reyes6 min read9 views

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.

Dim home office at night with a laptop screen glowing lime green, showing an abstract allow, ask, and deny permission list on a deep navy background
Dim home office at night with a laptop screen glowing lime green, showing an abstract allow, ask, and deny permission list on a deep navy background
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.

Anthropic logo 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:

  • deny is checked first and wins over everything.
  • ask is next. It pauses and prompts you.
  • allow runs 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:

json
"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:

json
"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:

  1. Enterprise or managed policy (if your org sets one).
  2. Command-line flags for the current run.
  3. Project .claude/settings.local.json (your personal, untracked rules).
  4. Project .claude/settings.json (checked into the repo, shared with the team).
  5. 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:

json
{
  "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 WebFetch allow. Too noisy, and I would rather see when it reaches out.
  • Bash(git commit:*) sitting in ask. 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.

Dani Reyes

Written by

Dani Reyes

Indie 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.

AI dev workflow

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.

6 min read47