Skip to content

Permissions, Approvals, and Safe Tool Use

Claude asks before destructive actions. Understand permission modes, sandboxing, and why this matters before you go further.

Claude Code can read your files, run shell commands, and edit your code. That's a lot of power to hand over. The permission system is what stands between you and "why did it just delete my database."

How the Permission Tiers Work

Not every action carries the same risk. Reading a file is harmless. Deleting one is not. The permission system groups tools into tiers based on how much damage they could do.

Read-only tools (file reads, grep, glob) never ask for permission. They can't change anything. Claude Code uses these freely to understand your codebase.

File modifications (edits and writes) require your approval. Claude Code shows you the diff and waits. If you approve, that permission lasts until the end of your session. Next session, it asks again.

Bash commands are the scary tier. Shell commands can do literally anything, so Claude Code prompts you every time. When you approve one, you get an extra option: "Yes, don't ask again." This permanently allowlists that specific command pattern for the current project. Saves to your settings.

Three tiers. Tight where it matters, out of your way where it doesn't.

Permission Modes

Five modes controlling how aggressive the prompting is.

default is what you get out of the box. Prompts on first use of each tool type, following the tiers above.

acceptEdits auto-approves all file edits for the session but still prompts for bash commands. This is where most people land once they're comfortable. Trust the edits (you can review diffs in git afterward), keep an eye on shell commands.

plan is read-only. Claude Code can analyze your code, answer questions, make plans, but can't modify files or execute commands. Brainstorming mode.

dontAsk is the paranoid option. Auto-denies any tool not already pre-approved in your settings. Set up your allowlist ahead of time, Claude Code stays strictly within those bounds.

bypassPermissions skips all prompts entirely. Only use this in fully isolated environments like Docker containers or CI pipelines. Never on your local machine. Seriously.

Set the mode when launching:

claude --permission-mode plan

Or set a default in your settings file:

{
  "defaultMode": "acceptEdits"
}

Fine-Grained Permission Rules

Beyond modes, you can set rules for individual tools. Three types: Allow (no prompting), Ask (prompt for confirmation), Deny (block completely).

Evaluation order matters. Deny is checked first, then Ask, then Allow. Deny always wins. So if you allow Bash(git *) but deny Bash(git push *), the deny wins for push commands. Be liberal with allows, but always deny the dangerous stuff explicitly.

Rule syntax: Tool or Tool(specifier). For Bash, wildcards with *. For file tools (Read, Edit), gitignore-style patterns where //path means absolute, ~/path means home directory, /path means project root. For WebFetch, specify a domain: WebFetch(domain:example.com). For MCP server tools: mcp__servername__toolname.

A typical settings file:

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(git commit *)",
      "Bash(git status)",
      "Bash(git diff *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Bash(rm -rf *)"
    ]
  }
}

npm scripts and git commits: fine. Pushing to remote or rm -rf: blocked. Everything else follows the normal prompting behavior.

/permissions inside a Claude Code session shows all your current rules and which settings file they came from.

Try It: Set Up Your Own Rules

  1. Start a session in plan mode:
claude --permission-mode plan
  1. Ask it to analyze some code. Notice it reads files but refuses to change anything.

  2. Exit, restart in default mode. Ask it to run npm run build. When prompted, choose "Yes, don't ask again." That command is now permanently allowlisted.

  3. Run /permissions to see your new rule.

  4. Open .claude/settings.json and look at the permissions block. You'll see the rule you just created. Add a deny rule for Bash(git push *) by hand, restart, verify Claude Code respects it.

This is how you build up a permission profile that fits your workflow. Takes a few sessions to dial in, but once it's set, you barely notice the permission system anymore.

Dealing with Prompt Fatigue

If you click "Yes" on the same commands over and over, eventually you stop reading what you're approving. At that point the permissions aren't protecting you from anything.

Fix it. Allowlist your safe commands: build scripts, test runners, linters, git status. Keep rm, git push, and anything touching production off the allow list.

Permissions exist at three levels. Per-user settings apply everywhere. Per-project settings (.claude/settings.json) apply only to that codebase. Organizations can push defaults too. More specific wins, so a project can tighten rules that user-level config left open.

acceptEdits is your best tool against prompt fatigue. Batch-approves all file edits, still prompts for shell commands. Since file edits give you a diff you can review after the fact with git, auto-approving them is much lower risk than auto-approving arbitrary bash commands.

What Matters

  • Three tiers: read-only (no prompt), file modifications (session-scoped), bash commands (permanent approval option)
  • Five modes from fully locked (plan) to fully open (bypassPermissions). Most people land on acceptEdits.
  • Deny always beats Allow when rules conflict
  • Allowlist your safe commands. Deny your dangerous ones. /permissions to audit what you've got.

Mastering Claude Code

Lesson 4 of 70