Claude Dev Guide
Learning from scratch? This concept is introduced in Chapter 3: When Claude goes off the rails →
T1 Claude Code

Permissions & allowed-tools

Scope what Claude can do per project — the difference between a useful agent and an unsupervised one.

Permissions in Claude Code control which tools Claude can use, which paths it can read or write, and how much human approval is required for each action. Getting this right is the difference between a productive session and one where you’re approving every file read.

Permission modes

Set via defaultMode in settings.json or per-invocation with --permission-mode:

ModeBehavior
defaultPrompts on every tool use
acceptEditsAuto-approves file edits; prompts for Bash
planShows a plan before execution; user confirms
autoAI classifier auto-approves low-risk operations
dontAskAuto-approves all tool use
bypassPermissionsSkips all permission checks

auto mode is the right default for trusted projects. It uses an AI classifier to distinguish “read a file” (low risk, auto-approve) from “run a shell command” (higher risk, prompt). For most development work it gives a good balance.

Warning

auto mode is silently ignored if set in .claude/settings.json or .claude/settings.local.json. This is intentional: a project repo shouldn’t be able to grant itself elevated permissions. Set auto mode in ~/.claude/settings.json (user-level) instead.

Granular rules

Use the permissions object in settings.json for specific allow/deny/ask rules:

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(flutter *)",
      "Bash(dart *)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Read(~/.zshrc)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push --force*)",
      "Bash(curl *)",
      "Read(./.env*)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(docker *)"
    ]
  }
}

Evaluation order: Deny → Ask → Allow. First match wins. If a command matches a deny rule, it’s blocked regardless of any allow rules.

Rule syntax: ToolName(pattern) where pattern is a glob. Bash(npm run *) matches any npm run ... command. Omitting the pattern ("Read") matches all uses of that tool.

Scope hierarchy

Rules from all scopes are merged (not overridden):

ScopeFileShared?
Managed (org)MDM-configuredEnforced
User~/.claude/settings.jsonNo
Project.claude/settings.jsonYes (git-tracked)
Local.claude/settings.local.jsonNo (gitignore it)

Deny rules from any scope block regardless of allow rules from another scope. A user-level deny overrides a project-level allow.

Per-invocation flags

For CI or one-off runs, use flags instead of settings files:

# Allow specific tools only
claude -p "run tests and report" \
  --allowedTools "Bash(npm test)" "Read" "Bash(npm run lint)"

# Disallow specific tools
claude -p "review the code" \
  --disallowedTools "Bash" "Write" "Edit"

These flags are per-invocation and don’t affect your settings.json.

Practical configuration

Minimal project settings.json — approve edit-heavy work but block network and destructive commands:

{
  "defaultMode": "acceptEdits",
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(flutter *)",
      "Bash(dart format *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git add *)",
      "Bash(git log *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(git push --force*)"
    ],
    "ask": [
      "Bash(git commit *)",
      "Bash(git push *)"
    ]
  }
}

User ~/.claude/settings.json — global defaults for your machine:

{
  "defaultMode": "auto",
  "permissions": {
    "deny": [
      "Bash(sudo *)",
      "Bash(rm -rf /*)
    ]
  }
}

When to lock things down

Start with acceptEdits on a new project. As you build confidence with what Claude does, loosen toward auto. Tighten again if:

  • Claude starts touching paths you didn’t expect
  • You’re working near production configuration
  • You’re doing a risky refactor and want more visibility

Never use bypassPermissions for development work — it’s for automated CI pipelines that have already been audited. For those, see Headless mode / CI.

Common pitfall: setting deny rules in project settings and wondering why auto mode isn’t working

auto mode must be in user settings. Project settings can add deny rules but can’t elevate trust — only restrict it. If you set "defaultMode": "auto" in .claude/settings.json, it’s silently ignored and the session falls back to default (prompt on everything). The correct split: trust escalation in user settings, project-specific restrictions in project settings.