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
| Mode | Flag | Behavior | Use Case |
|---|---|---|---|
default | (none needed) | Prompts for permission on first use of each tool | Interactive development |
acceptEdits | —permission-mode acceptEdits | Auto-approves file edits, prompts for shell commands | Trusted editing sessions |
plan | —permission-mode plan | Read-only — can analyze but NOT modify files or run commands | Code review, architecture analysis |
dontAsk | —permission-mode dontAsk | Auto-denies tools unless pre-approved via allow rules | Headless / automated workflows |
bypassPermissions | —permission-mode bypassPermissions | Skips all permission prompts — every tool call succeeds | CI/CD, containers, VMs only |
auto | —permission-mode auto | AI classifier auto-approves safe actions, blocks risky ones (7 allow + 26 deny rules) | Future default — not yet available (API beta gate) |
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
| Scenario | Mode | Why |
|---|---|---|
| Interactive development, first time on a project | default | You want to see and approve each action until you build trust |
| Focused coding session, trusted codebase | acceptEdits | File edits flow freely; shell commands still require a nod |
| Code review or planning only | plan | Claude analyzes without modifying anything |
| Automated pipeline with explicit allow rules | dontAsk | Fails fast on unapproved tools instead of blocking on a prompt |
| CI/CD in a container or VM | bypassPermissions | No human present, isolated environment limits blast radius |
| Plan-then-execute workflow | plan then bypassPermissions | Plan 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.
# Only allow file reads and grep — nothing elseclaude -p "Analyze auth.py" --allowedTools "Read,Grep,Glob"
# Allow everything except shell accessclaude -p "Refactor this module" --disallowedTools "Bash"
# Allow specific bash commands with glob patternsclaude -p "Run the tests" --allowedTools "Bash(npm test:*),Read,Edit"Tool specifiers use the same pattern syntax as permission rules:
Bash(git:*)— matchesgit 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.
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:
acceptEdits mode also blocks the write — but Claude retries once before giving up, producing two denials and an extra turn:
bypassPermissions mode succeeds immediately — zero denials, lowest cost:
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.
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.
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.
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.
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.
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
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)
| Level | Source | Scope |
|---|---|---|
| 1 | Managed settings (enterprise MDM) | Organization-wide, cannot be overridden |
| 2 | CLI arguments (—allowedTools, —disallowedTools) | Current session only |
| 3 | Local project settings (.claude/settings.local.json) | Per-developer, gitignored |
| 4 | Shared project settings (.claude/settings.json) | Team-wide, committed to repo |
| 5 | User 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.
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:
# Inline JSON — override a project deny rule for this sessionclaude -p "Write deployment notes" \ --settings '{"permissions":{"allow":["Write"]}}' \ --permission-mode bypassPermissions
# File-based — same effectclaude -p "Write deployment notes" \ --settings ./ci-overrides.json \ --permission-mode bypassPermissionsExperimentally 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.
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.
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.
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).
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.