The Landscape

Permission Modes

Mixed10 min

Why permissions matter

Claude Code has real access to your filesystem and shell. It can read files, write files, run commands, and make network requests. Permissions are how you control the blast radius.

The permission system has two layers:

  1. Modes - preset permission levels you can switch between
  2. Settings - granular per-tool allow/ask/deny lists

The five permission modes

ModeFile readsFile writesShell commandsBest for
defaultAllowedAsk each timeAsk each timeLearning, exploration
acceptEditsAllowedAuto-acceptAsk each timeTrusted edit sessions
planAllowedBlockedBlockedCode review, planning
dontAskAllowedAuto-acceptAuto-acceptExperienced users, automation
bypassPermissionsAllowedAuto-acceptAuto-acceptCI/CD pipelines only

default

The starting mode. Claude reads freely but asks permission before writing files or running commands. You see a prompt like:

Claude wants to edit src/auth.ts
[Allow] [Deny] [Always allow]

Good for: learning Claude Code, working in unfamiliar codebases, pair programming.

acceptEdits

File edits are auto-accepted. Shell commands still require approval. This is the sweet spot for most development work - you trust Claude's code changes but want to approve any rm, git push, or npm install.

plan

Read-only. Claude can read your codebase and propose a plan, but cannot modify anything. Use this when you want Claude to analyze and suggest without touching anything.

claude --permission-mode plan
> How would you refactor the auth module?

Claude will read files, analyze the code, and propose a detailed plan
with specific file changes - but won't execute any of them.

dontAsk

Everything is auto-approved. Claude reads, writes, and runs commands without asking. Use this when you trust the task and want Claude to work autonomously.

bypassPermissions

Like dontAsk but also bypasses system-level restrictions. Only available via CLI flag:

claude --dangerously-skip-permissions
bypassPermissions is for CI/CD only

This mode is named dangerously for a reason. It skips all safety checks. Only use it in sandboxed environments like CI/CD pipelines or Docker containers where the blast radius is contained. Never use it on your local machine with important data.

Switching modes

In interactive mode: press Shift+Tab to cycle through modes. The current mode shows in the prompt indicator.

Via CLI flag:

claude --permission-mode plan
claude --permission-mode acceptEdits
claude --dangerously-skip-permissions

The "Always allow" shortcut: when Claude asks for permission in default mode, choosing "Always allow" adds that specific tool to your allow list for the session.

Settings-based permissions

For finer control, configure permissions in .claude/settings.json (shared with team, committed to git) or .claude/settings.local.json (personal, gitignored):

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(npm test)",
      "Bash(npm run lint)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push --force)"
    ]
  }
}

The three arrays:

  • allow - always permitted, no prompt
  • ask - prompt before each use (default for most tools)
  • deny - always blocked, even if the mode would allow it

You can be specific with shell commands. Bash(npm test) allows only that exact command. Bash(npm run *) allows any npm run script.

Layer your permissions

Start restrictive, open up as you build trust. Begin in default mode, use "Always allow" for commands you approve repeatedly, and graduate to acceptEdits or dontAsk as you get comfortable. The settings file lets you codify team-wide safety rails.

Recommended setup for teams

Share safety rails in .claude/settings.json (committed to git):

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Bash(npm run build)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push --force)",
      "Bash(curl * | sh)"
    ]
  }
}

Individual developers can add personal overrides in .claude/settings.local.json (gitignored).

Ex

Explore permission modes

  1. Start Claude Code in default mode: claude
  2. Ask Claude to create a new file. Notice the permission prompt.
  3. Press Shift+Tab to switch to plan mode
  4. Ask Claude to refactor something. Notice it only plans, doesn't execute.
  5. Press Shift+Tab to switch to acceptEdits mode
  6. Ask Claude to make a small edit. Notice it applies without asking.
  7. Run /cost to see your usage
The current permission mode is displayed in the input prompt area. Watch it change as you press Shift+Tab.

In default mode, you'll see prompts like "Claude wants to write to file.ts - Allow / Deny". In plan mode, Claude will describe what it would do but make no changes. In acceptEdits mode, file changes apply immediately but shell commands still ask.

The mode indicator in the prompt shows: > (default), >> (acceptEdits), ? (plan), >>> (dontAsk).

Ex

Create a settings file

Create a .claude/settings.json for one of your projects:

  1. mkdir -p .claude
  2. Create the settings file with allow/deny rules appropriate for your project
  3. Start Claude Code and verify the rules work

Think about: what commands should always be safe? What commands should never run?

Common safe commands: test runners, linters, type checkers, build commands. Common denied commands: force pushes, recursive deletes, production deployments.

A solid starting point:

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Bash(npm run build)",
      "Bash(npx tsc --noEmit)"
    ],
    "deny": [
      "Bash(git push --force*)",
      "Bash(rm -rf *)"
    ]
  }
}

Demo the Shift+Tab switching live. Start in default mode, make an edit (show the prompt), switch to plan mode (show the blocked execution), switch to acceptEdits (show auto-approved edits). The visual difference between modes lands better as a live demo than as text.