Skip to content

Permission Modes

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

10 min read

You deploy a Claude agent to CI. It hangs. No error, no timeout — just sitting there, waiting for a permission prompt that nobody is there to answer. The fix is one flag. But pick the wrong flag and you’ve just given Claude unrestricted access to your production server.

Six permission modes control how much autonomy Claude gets — from asking before every action, to executing everything silently. Combined with tool-level allowlists and denylists, they form the permission system that determines exactly what Claude can and cannot do.

The Six 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
auto—permission-mode autoAI classifier auto-approves safe actions, blocks risky ones (7 allow + 26 deny rules)Future default — not yet available (API beta gate)
Permission Mode Spectrum
MORE CONTROL read-only MORE AUTONOMY full access plan read only default prompt each acceptEdits auto-approve edits dontAsk deny unknown bypass skip all checks interactive dev CI / containers

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.

auto is the most sophisticated mode. Instead of static tool-based rules, it uses an AI safety classifier with 7 allow rules (local ops, read-only, declared dependencies…) and 26 deny rules (data exfiltration, production deploys, credential exploration…) plus configurable trust boundaries. Inspect the classifier with claude auto-mode config and claude auto-mode defaults. Currently gated behind an unreleased API beta (afk-mode-2026-01-31) — the CLI is ready but the server-side classifier hasn’t shipped yet.

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 six 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, etc.
  • 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.

For details on how permission rules interact across scopes, including precedence, --settings overrides, and merge behavior, see Permission Precedence below.

Try This

Before reading the comparison below, try this: run the same prompt in plan mode and then in default mode.

claude -p “Write hello to /tmp/test.txt” —permission-mode plan —output-format json | jq ‘.permission_denials’

What shows up in permission_denials? Now try the same prompt without —permission-mode. What changes?

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

Yes, —dangerously-skip-permissions sounds terrifying. That’s the point — it’s the same flag as bypassPermissions, just named to scare you. And it should. 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.

Note

The auto permission mode exists in the CLI but requires an API beta (afk-mode-2026-01-31) that hasn’t shipped yet. You can inspect its classifier rules with claude auto-mode config — they reveal 26 deny rules covering data exfiltration, production deploys, credential exploration, and more. When the beta ships, auto may become the recommended mode for most workflows.

Now Do This

Add —permission-mode plan to one of your scripts. Run it. Check the permission_denials array in the JSON output — you’ll see exactly what Claude wanted to do but couldn’t. That array is your audit trail.

Permission Precedence

Permission precedence: CLI flags, project settings, user settings, defaultsPRIORITYCLI Flags--allowedTools, --disallowedToolsProject Settings.claude/settings.jsonUser Settings~/.claude/settings.jsonDefaultsBuilt-in defaultsoverridesoverridesoverrides

You add an allow rule for Write in your project settings. It doesn’t work — a managed deny rule at the org level overrides it silently. Permission rules have a precedence hierarchy, and the rules you set might not be the rules that actually apply.

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.

Try This

Test rule precedence by creating conflicting rules at different scopes. Add an allow rule in your project .claude/settings.json and a deny rule via the CLI flag:

claude -p “Write hello to test.txt” —disallowedTools “Write” —output-format json | jq ‘.permission_denials’

Which rule won — the project allow or the CLI deny? This demonstrates the deny-always-wins principle.

--settings Runtime Override

The --settings flag loads additional settings from inline JSON or a file path, creating a new precedence layer above project settings:

Terminal window
# Inline JSON — override a project deny rule for this session
claude -p "Write deployment notes" \
--settings '{"permissions":{"allow":["Write"]}}' \
--permission-mode bypassPermissions
# File-based — same effect
claude -p "Write deployment notes" \
--settings ./ci-overrides.json \
--permission-mode bypassPermissions

Experimentally confirmed: --settings with allow:["Write"] overrides a project .claude/settings.json that denies Write. The file was created successfully with zero permission_denials. Both inline JSON and file-based formats behave identically.

--settings composes with --allowedTools — both flags work together without conflict. --settings affects the settings precedence layer while --allowedTools operates on the tool filter layer.

--settings Can Override Project Deny Rules

Unlike —allowedTools vs —disallowedTools (where deny always wins at the same level), —settings operates at a higher precedence level than project settings. A —settings allow CAN override a project-level deny. Treat this flag with the same caution as —permission-mode bypassPermissions.

Array Settings Merge Behavior

When settings are loaded from multiple scopes (user, project, local, CLI), array values like permissions.allow and permissions.deny are concatenated and deduplicated, not replaced. A project-level allow: ["Read"] combined with a user-level allow: ["Write"] results in allow: ["Read", "Write"].

This merge-and-deduplicate strategy applies to all array-type settings across all scopes.

Array Settings Merge Behavior

When settings are loaded from multiple scopes (user, project, local, CLI), array values like permissions.allow and permissions.deny are concatenated and deduplicated, not replaced. A project-level allow: [“Read”] combined with a user-level allow: [“Write”] results in allow: [“Read”, “Write”]. This merge-and-deduplicate strategy applies to all array-type settings across all scopes.

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).

Now Do This

Run claude -p “Write hello to test.txt” —output-format json | jq ‘.permission_denials’ and check what rules are actually applied. If you see unexpected denials, trace them through the precedence hierarchy: managed > CLI flags > local project > shared project > user. The deny you can’t find is usually at a higher scope than you’re looking.