Overview

Permissions combine a mode, which sets the baseline for what runs without a prompt, with rules that pre-approve or block specific tool calls. Claude Code enforces both; instructions in a prompt or CLAUDE.md shape what Claude tries but do not change what is allowed. For enforcement beyond text patterns see the sandbox and hook sections below.

Pick a mode for the trust level

ModeRuns without askingUse for
default (shown as Manual; alias manual)Reads onlySensitive or unfamiliar work
acceptEditsReads, file edits, mkdir, touch, rm, rmdir, mv, cp, sed inside the working directoryReviewing diffs after the fact
planReads and exploration; edits blocked until you approve a planScoping before changes
autoEverything, with a classifier model blocking risky actionsLong tasks, fewer prompts
dontAskReads and pre-approved tools; everything else is deniedCI and locked-down scripts
bypassPermissionsEverything (deny rules and hooks still apply)Disposable containers and VMs only

From v2.1.283 auto is the built-in starting mode in interactive terminal and VS Code sessions. It needs a supported model (on the Anthropic API, Opus 4.6 or later, Sonnet 4.6 or later, or a Fable model; on Bedrock, Vertex, and Foundry, Sonnet 5 or later, Opus 4.7 or later, or Fable) and an administrator who has not set permissions.disableAutoMode. Auto reduces prompts but does not guarantee safety.

Set the starting mode with --permission-mode or permissions.defaultMode. A defaultMode of auto or bypassPermissions in a project or local settings file is ignored; set those in ~/.claude/settings.json. Shift+Tab cycles default, acceptEdits, plan, then auto and bypass when enabled; dontAsk is never in the cycle. bypassPermissions also exists as --dangerously-skip-permissions, refuses root outside a sandbox, and is ignored from settings files in cloud sessions, as is dontAsk.

Write rules as Tool or Tool(specifier)

Rules evaluate deny, then ask, then allow; the first match wins and specificity does not change the order. A deny at any settings level cannot be overridden by an allow elsewhere.

For Bash, * matches any text including spaces. Put it after the subcommand, because everything before the first * is matched as written.

RuleMatchesDoes not match
Bash(npm run build)exactly that commandnpm run build --watch
Bash(git log *)git log, git log --onelinegit status
Bash(ls *)ls, ls -lalsof
Bash(ls*)ls -la, lsof

Compound commands (&&, ||, ;, |, &) are split and each part must match. Wrappers timeout, time, nice, nohup, and stdbuf are stripped first; npx and docker exec are not, so write the inner command into the rule. The read-only set (ls, cat, grep, find, git status, and similar) already runs without a prompt in every mode, so do not allowlist it.

Read and Edit rules use gitignore patterns: //abs/path is absolute, ~/path is home, /path is relative to the settings source, ./path or path is relative to the working directory. As an allow rule Read(src/**) matches only <cwd>/src; as a deny or ask rule it matches a src directory at any depth. A Read deny also blocks Edit and Write on that path. Write(...) path rules are never consulted; use Edit(...). Other forms: WebFetch(domain:example.com), Agent(Explore), and mcp__server__tool (see claude-code-mcp).

{
  "permissions": {
    "allow": ["Bash(npm run *)", "Bash(git commit *)"],
    "deny": ["Bash(git push *)", "Read(./.env)", "Read(./secrets/**)"]
  }
}

Treat deny rules as guardrails, not boundaries

A Bash rule matches the command text Claude writes. Bash(rm *) does not stop /bin/rm or bash -c 'rm ...'; Bash(git push *) does not stop git -C . push. Patterns that constrain arguments are fragile: Bash(curl http://github.com/ *) misses options before the URL, https, redirects, and variables. Read and Edit denies cover built-in file tools and recognized commands such as cat and sed, not a Python script that opens the file itself.

When a restriction must hold, use the OS-level sandbox (/sandbox), a PreToolUse hook that inspects the full command (see claude-code-hooks), or deny curl and wget and allow WebFetch(domain:...) alongside a sandbox network allowlist.

Scope rules per project and commit them

User rules live in ~/.claude/settings.json, shared project rules in .claude/settings.json, and personal project rules in .claude/settings.local.json; managed settings outrank all of them. Project allow rules apply only after you accept the workspace trust dialog. Do not put secrets in any of these files.

Build the allowlist from observed prompts. Choosing “Yes, and don’t ask again” saves a rule (up to five for a compound command), and /permissions lists every rule with its source file. Allowlist the build, test, and lint commands you approve every session; leave write-capable tools on prompt.

Use dontAsk with an exact allowlist in CI

A -p run with no mode set takes the built-in starting mode, which can be auto, so pass the one you want.

claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read"

--allowedTools uses the same rule syntax. Add --permission-prompts none (v2.1.259 and later) for unattended runs: anything that would prompt is denied instead of waiting on an SDK canUseTool callback or --permission-prompt-tool. See claude-agent-sdk.