Claude Code

Permission rules in Claude Code: allow, ask, deny and evaluation order

TL;DR

Permission rules tell Claude Code what it may do without asking, must ask about, or may never do. Deny always beats ask and ask beats allow at every level; Bash rules aren't an absolute fence, so use the sandbox for must-block cases.

Clicking "Yes" for every npm test is tiring, while turning on a mode that skips all permissions is scary. The middle ground is permission rules: write rules up front for what Claude Code may do without asking, must ask about, or is never allowed to do. Rules can be committed to the repo for the whole team, and each person can still add their own. Understanding the evaluation order decides whether your rules actually block anything.

Permission modes

ModeBehaviour
default (Manual)Asks on first use of each tool
acceptEditsAuto-accepts file edits and common filesystem commands (mkdir, mv, cp…) in the working directory
planReads and explores only; doesn't edit source files
autoNo routine prompts; a background classifier checks risky actions before they run
dontAskAuto-denies anything that would prompt; only pre-allowed actions run
bypassPermissionsSkips prompts (except a few actions no mode auto-approves); only for isolated containers/VMs

Rule syntax

{
  "permissions": {
    "allow": ["Bash(npm run *)", "Bash(git commit *)", "WebFetch(domain:docs.python.org)"],
    "ask":   ["Bash(git push *)"],
    "deny":  ["Read(./.env)", "Read(./secrets/**)", "Bash(curl *)", "mcp__prod-db__*"]
  }
}
  • Bash: * matches any text including spaces. Put the * after the subcommand: Bash(git log *) allows only git log, while Bash(git *) allows every git command.
  • Read/Edit: gitignore-style paths. A .claudeignore file has no effect; move its entries into Read deny rules.
  • WebFetch: domain:example.com doesn't cover subdomains; add domain:*.example.com if needed.
  • MCP: mcp__server__* for a whole server, mcp__server__tool for one tool.

Evaluation order: deny → ask → allow

This is the most misunderstood part. Per the docs, rules are evaluated deny, then ask, then allow; the first match wins, and rule specificity doesn't change the order.

  • Denying Bash(aws *) also blocks aws s3 ls even if you allow Bash(aws s3 ls). An allow can't carve an exception out of a deny.
  • A deny at any level (user, project, managed) beats an allow at another level.
  • Organisation managed settings are highest: no level, not even command-line arguments, can override them.
  • A PreToolUse hook returning "allow" doesn't bypass deny or ask either.

Risks and caveats

  • Bash rules aren't an absolute fence: denying Bash(git push *) doesn't match git -C . push; name-based denies don't match a program called by path or inside sh -c. For must-block cases, combine with the sandbox.
  • Read denies don't block every read: a Python script that opens a file itself isn't stopped by the rule. OS-level protection needs the sandbox.
  • Overly broad allows: Bash(python *) allows almost anything, since Python can run arbitrary code.
  • bypassPermissions on a real machine: only in isolated environments; organisations can disable it with disableBypassPermissionsMode.

How to apply it

  • Dev: Allow the test/lint/build commands you run daily; deny reading .env and secrets folders from day one.
  • Tech Lead: Commit .claude/settings.json with the team's shared allow/deny set; keep git push and deploy commands under ask.
  • PM/Admin: Use managed settings for company-wide mandatory rules (no reading customer folders, bypass disabled), since nobody can override them.

Sources & references

  1. Configure permissions — Claude Code Docs