Permission rules

Claude Code · Practical · 14 min

Rules cut prompt fatigue for safe commands while keeping dangerous ones blocked in every mode.

Modes set the baseline; permission rules fine-tune it. Rules live in a settings file under permissions, in three lists:

  • allow: run without asking (for example your test command),
  • ask: always ask, even in modes that would auto-approve,
  • deny: never allowed, in any mode.

They are checked in that safety order: deny first, then ask, then allow. A broad deny always wins over a narrower allow.

A rule names a tool and optionally a specifier in parentheses:

  • Bash(npm run test *): any command starting with npm run test. The * is a wildcard; the space before it matters (Bash(ls *) matches ls -la but not lsof).
  • Read(./.env) and Read(./secrets/**): file paths, written like .gitignore patterns. Edit(...) works the same for writes.
  • WebFetch(domain:docs.python.org): web fetches to one domain.
  • mcp__github__*: all tools from one MCP server named github.
  • A bare tool name such as Bash matches every use.

Bash rules understand shell operators: allowing Bash(npm test *) does not allow npm test && rm -rf build. But matching is by command text, so a deny on Bash(git push *) does not catch git -C . push. Treat Bash denies as guard rails, not a security boundary.

Manage rules with /permissions, which lists each rule and the file it came from, or edit settings files directly. Choosing "yes, don't ask again" at a prompt writes an allow rule for you. /fewer-permission-prompts can suggest an allowlist from your history.

Rules are enforced by Claude Code, not by the model, so they hold even if a prompt or file tries to talk Claude into something.

Example

{
  "permissions": {
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(git diff *)",
      "WebFetch(domain:developer.mozilla.org)"
    ],
    "ask": [
      "Bash(git push *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(curl *)"
    ]
  }
}

Remember

  • allow, ask and deny lists are checked deny, then ask, then allow
  • Specifiers: Bash(cmd ), Read/Edit(path patterns), WebFetch(domain:...), mcp__server__
  • Rules are enforced by Claude Code itself; manage them with /permissions

Watch out for

  • Writing an allow rule like Bash(git *) to stop prompts, which also pre-approves git push, reset and clean.

Check yourself

  1. Your settings deny Bash(aws *) and allow Bash(aws s3 ls). Does `aws s3 ls` run without asking?
  2. Which rule stops Claude's Read tool from opening .env in the project root?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: code.claude.com/docs/en/permissions