Permission Modes

Control what Claude can do — modes, allowlists, and denylists

Claude Code has five permission modes that control how much autonomy it gets — from asking before every action, to executing everything without a prompt. The right mode depends on context: interactive coding sessions need guardrails, CI/CD pipelines need full autonomy, and code reviews need read-only access. Combined with tool-level allowlists and denylists, these modes form a layered permission system that determines exactly what Claude can and cannot do.

The Five Modes

Every claude session runs in exactly one permission mode. You set it with --permission-mode MODE or accept the default.

Permission Modes

ModeFlagBehaviorUse Case
default(none needed)Prompts for permission on first use of each toolInteractive development
acceptEdits—permission-mode acceptEditsAuto-approves file edits, prompts for shell commandsTrusted editing sessions
plan—permission-mode planRead-only — can analyze but NOT modify files or run commandsCode review, architecture analysis
dontAsk—permission-mode dontAskAuto-denies tools unless pre-approved via allow rulesHeadless / automated workflows
bypassPermissions—permission-mode bypassPermissionsSkips all permission prompts — every tool call succeedsCI/CD, containers, VMs only

default is what you get when you launch claude with no flags. Every destructive tool — Bash, Edit, Write — requires one-time approval. Once approved, the rule persists for the session (file edits) or permanently per project (bash commands). Read-only tools like Grep and file reads never require permission.

acceptEdits trusts Claude with file modifications inside the project directory. Bash commands still require approval. This is the sweet spot for focused coding sessions where you want speed but not unlimited shell access.

plan is read-only. Claude can read files, search code, and reason — but cannot write files or execute commands. Use this for code review, architecture analysis, or when you want Claude to produce a plan without touching anything.

dontAsk is the inverse of default. Instead of prompting for unknown tools, it silently denies them. Only tools explicitly listed in permission allow rules will work. This is designed for headless or automated workflows where there is no human to answer prompts.

bypassPermissions disables all permission checks. Every tool call succeeds without prompting. This should only be used in isolated environments — CI/CD pipelines, containers, or virtual machines where the blast radius is contained.

Choosing a Mode

The decision comes down to two questions: is a human watching, and how much do you trust the environment?

Mode Selection Guide

ScenarioModeWhy
Interactive development, first time on a projectdefaultYou want to see and approve each action until you build trust
Focused coding session, trusted codebaseacceptEditsFile edits flow freely; shell commands still require a nod
Code review or planning onlyplanClaude analyzes without modifying anything
Automated pipeline with explicit allow rulesdontAskFails fast on unapproved tools instead of blocking on a prompt
CI/CD in a container or VMbypassPermissionsNo human present, isolated environment limits blast radius
Plan-then-execute workflowplan then bypassPermissionsPlan in read-only, review output, resume with full access

A common pattern is combining modes with sessions. Plan in plan mode, review the output, then --resume the same session in bypassPermissions to execute. The Session Chains chapter covers this workflow in detail.

—allowedTools / —disallowedTools

Beyond the five modes, you can filter at the individual tool level. These flags accept comma-separated tool names and support pattern matching.

Terminal window
# Only allow file reads and grep — nothing else
claude -p "Analyze auth.py" --allowedTools "Read,Grep,Glob"
# Allow everything except shell access
claude -p "Refactor this module" --disallowedTools "Bash"
# Allow specific bash commands with glob patterns
claude -p "Run the tests" --allowedTools "Bash(npm test *),Read,Edit"

Tool specifiers use the same pattern syntax as permission rules:

  • Bash(git *) — matches git status, git commit, but NOT gitk
  • Edit(/src/**/*.ts) — matches any TypeScript file under /src/
  • mcp__puppeteer__* — matches all tools from the puppeteer MCP server

These flags are temporary and apply only to the current session. They combine with the permission mode — a tool must pass both the mode check and the allowlist/denylist check to execute.

Precedence Rules

Permission rules can be defined at multiple levels. When rules conflict, the higher-precedence level wins. Deny always beats allow at the same level.

Settings Precedence (Highest to Lowest)

LevelSourceScope
1Managed settings (enterprise MDM)Organization-wide, cannot be overridden
2CLI arguments (—allowedTools, —disallowedTools)Current session only
3Local project settings (.claude/settings.local.json)Per-developer, gitignored
4Shared project settings (.claude/settings.json)Team-wide, committed to repo
5User settings (~/.claude/settings.json)Global defaults for the user

The critical rule: deny > ask > allow at every level. If a tool is denied at ANY level, no lower-level allow rule can override it. A team lead can deny Bash(rm -rf *) in shared project settings, and no individual developer’s local settings or CLI flags can re-enable it.

Modes in Action — Same Prompt, Three Outcomes

To see how modes affect behavior, here is the same prompt run under three different modes: “Write hello to /tmp/perm_test.txt” — a write to a path outside the project directory.

default mode blocks the write and reports one denial:

default mode — Write blockedartifacts/03/default_mode.json
1{
2 "type": "result",
3 "subtype": "success",
4 "is_error": false,
5 "duration_ms": 6611,
6 "num_turns": 2,A
7 "result": "It looks like permission to write to `/tmp/perm_test_default.txt` was denied. This is outside the project directory, so you'd need to grant permission for that path.",
8 "session_id": "14325d7c-c649-41b1-83a1-5b2b08c38184",
9 "total_cost_usd": 0.027987,
10 "permission_denials": [
11 {
12 "tool_name": "Write",B
13 "tool_use_id": "toolu_01ET8KmMAWVY5WqSDxsne9mZ",
14 "tool_input": {C
15 "file_path": "/tmp/perm_test_default.txt",
16 "content": "hello\n"
17 }
18 }
19 ]
20}
A2 turns: one attempt, one explanation
BSingle denial — Claude did not retry
CThe exact Write call that was blocked

acceptEdits mode also blocks the write — but Claude retries once before giving up, producing two denials and an extra turn:

acceptEdits mode — Write still blocked (outside project)artifacts/03/accept_edits_mode.json
1{
2 "type": "result",
3 "subtype": "success",
4 "is_error": false,
5 "duration_ms": 6647,
6 "num_turns": 3,A
7 "result": "It seems write access to `/tmp/perm_test_accept.txt` is being blocked by your permission settings.",
8 "session_id": "1e0f7082-6e8f-4810-8e85-e40c516d735d",
9 "total_cost_usd": 0.038465,B
10 "permission_denials": [
11 {C
12 "tool_name": "Write",
13 "tool_use_id": "toolu_01RpQemEPzcTaV1tcxtrbZMA",
14 "tool_input": {
15 "file_path": "/tmp/perm_test_accept.txt",
16 "content": "hello\n"
17 }
18 },
19 {
20 "tool_name": "Write",
21 "tool_use_id": "toolu_01Ew64DqvyigeEs7Ue1VQAf7",
22 "tool_input": {
23 "file_path": "/tmp/perm_test_accept.txt",
24 "content": "hello\n"
25 }
26 }
27 ]
28}
A3 turns — one extra turn from the retry
BHigher cost due to the retry attempt
CTwo denials for the same action — Claude retried once

bypassPermissions mode succeeds immediately — zero denials, lowest cost:

bypassPermissions mode — Write succeedsartifacts/03/bypass_mode.json
1{
2 "type": "result",
3 "subtype": "success",
4 "is_error": false,
5 "duration_ms": 6191,
6 "num_turns": 2,
7 "result": "Done. Wrote \"hello\" to `/tmp/perm_test_bypass.txt`.",A
8 "session_id": "8962eff5-0fee-4dd6-aec1-b862af7f426d",
9 "total_cost_usd": 0.026981,C
10 "permission_denials": []B
11}
AConfirmation that the file was written
BEmpty array — zero permission denials
CLowest cost: no retries, no denial handling
Permission Mode Comparison
default mode
1{
2 "type": "result",
3 "subtype": "success",
4 "is_error": false,
5 "duration_ms": 6611,
6 "num_turns": 2,
7 "result": "It looks like permission to write to `/tmp/perm_test_default.txt` was denied. This is outside the project directory, so you'd need to grant permission for that path.",
8 "session_id": "14325d7c-c649-41b1-83a1-5b2b08c38184",
9 "total_cost_usd": 0.027987,
10 "permission_denials": [
11 {
12 "tool_name": "Write",
13 "tool_use_id": "toolu_01ET8KmMAWVY5WqSDxsne9mZ",
14 "tool_input": {
15 "file_path": "/tmp/perm_test_default.txt",
16 "content": "hello\n"
17 }
18 }
19 ]
20}
bypassPermissions mode
1{
2 "type": "result",
3 "subtype": "success",
4 "is_error": false,
5 "duration_ms": 6191,
6 "num_turns": 2,
7 "result": "Done. Wrote \"hello\" to `/tmp/perm_test_bypass.txt`.",
8 "session_id": "8962eff5-0fee-4dd6-aec1-b862af7f426d",
9 "total_cost_usd": 0.026981,
10 "permission_denials": []
11}
1.Compare permission_denials: blocked vs empty array
2.Cost difference from retry overhead in default mode

The key insight from these payloads: acceptEdits does NOT auto-approve writes outside the project directory. The sandbox filesystem restriction applies regardless of permission mode. The retry behavior also means higher cost — check permission_denials.length to understand what happened.

Gotcha

—dangerously-skip-permissions is just an alias for —permission-mode bypassPermissions. They are identical in behavior. The long name exists to make scripts self-documenting about the risk involved.

Gotcha

plan mode works by blocking the ExitPlanMode tool — the internal tool Claude would call to transition from planning to execution. Since read-only tools (file reads, Grep, Glob) never require permission, they continue to work. Claude can analyze everything; it just cannot act on its analysis.

Tip

The permission_denials array in the JSON response tells you exactly which tools were blocked and what arguments Claude attempted. Parse this in your automation to detect when Claude tried to do something it was not allowed to do — and log it for auditing.

Gotcha

Permission rules in .claude/settings.json are strictly local — they do not walk up the directory tree. A parent directory’s permissions.deny: [“Write”] does not apply to subfolder sessions, even without a child settings.json. This is different from CLAUDE.md and MCP configs, which do inherit upward. For monorepo-wide permission enforcement, use managed settings (enterprise-level).