Claude Code settings.json Permissions: A Cheat Sheet
Claude Code permissions: allow/deny/ask rules, defaultMode values and rule syntax in one place. claude doctor rejected defaultMode "readonly" as invalid.
- Permission rules are evaluated deny, then ask, then allow; the first match decides, regardless of how specific a rule is.
- defaultMode accepts default (manual), acceptEdits, plan, auto, dontAsk and bypassPermissions; bypassPermissions is only for isolated environments.
- A misspelled defaultMode value isn't used; claude doctor reports it, and a bare deny on a tool like Bash removes that tool entirely.
This is a quick reference for the permissions block in Claude Code’s settings.json. It covers allow, ask and deny, the defaultMode values, additionalDirectories, rule syntax, which settings file wins, and three example configs you can paste in. We checked every format against the official docs. In our test environment (macOS, Claude Code 2.1.303 and 2.1.304, jq 1.7.1, on 2026-10-19 and 2026-10-20), we only checked syntax, using jq and claude doctor. We did not test how the rules behave in a live session. For why rules are evaluated in this order and how to avoid over-broad allows, see Permission Design for AI Agents.
allow, ask and deny
| Key | What it does | Watch out for |
|---|---|---|
allow | Lets Claude use the tool without asking | Has no effect if a deny or ask rule also matches |
ask | Prompts every time | Still prompts even when a narrower allow rule matches |
deny | Blocks the tool | A bare tool name such as Bash removes the tool from Claude entirely |
Rules are evaluated deny first, then ask, then allow. The first match decides. How specific a rule is doesn’t change that order. If you deny Bash(aws *), allowing Bash(aws s3 ls) won’t get it through.
Source: https://code.claude.com/docs/en/permissions (checked 2026-10-20)
defaultMode values
| Value | Behavior |
|---|---|
default | Asks the first time each tool is used. Shown as Manual in the CLI; manual works as an alias |
acceptEdits | Auto-approves file edits and commands such as mkdir, touch, mv and cp inside the working directory |
plan | Reads files and runs read-only commands to explore. Doesn’t edit source |
auto | No routine prompts; a classifier checks each action against your request before it runs |
dontAsk | Auto-denies anything that would prompt. Allowed tools and actions that need no approval still run |
bypassPermissions | Skips prompts. Use only in isolated environments such as containers or VMs |
Source: https://code.claude.com/docs/en/permissions (checked 2026-10-20)
A misspelled value is not used. With "defaultMode": "readonly", claude doctor printed:
- <project>/.claude/settings.json › permissions.defaultMode: Invalid value. Expected one of: "acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"
"manual" raised no error.
Rule syntax
| You write | Matches | Watch out for |
|---|---|---|
Bash / Bash(*) | Every Bash command | As a deny rule, Bash becomes unavailable |
Bash(npm run build) | That exact command | Doesn’t match npm run build --watch |
Bash(npm run *) | Commands starting with npm run | Put * after the subcommand. Bash(git *) matches every git command |
Bash(npm run test:*) | Same as Bash(npm run test *) | :* works only at the end |
Read(./.env) | .env in the current directory | A bare filename pattern matches at any depth |
Edit(/src/**) | src/ relative to the settings file’s location | In project settings, that means <working directory>/src/ |
Read(//etc/**) | An absolute filesystem path | A single leading / is not absolute |
Read(~/.ssh/**) | A path under your home directory | Use this in user settings to cover every project |
WebFetch(domain:example.com) | example.com only | Add domain:*.example.com for subdomains |
mcp__github__* | Every tool from the github server | mcp__github means the same |
mcp__github__create_issue | That one tool | MCP rules with parentheses are skipped |
Only Read(...) and Edit(...) apply to files. Write(docs/**) is accepted but never consulted; write Edit(docs/**) instead. A Read deny also blocks Edit and Write on the same path. It doesn’t cover commands that read files without naming them, such as grep -r.
Source: https://code.claude.com/docs/en/permissions (checked 2026-10-20)
claude doctor reported the MCP rule with parentheses like this:
- <project>/.claude/settings.json › permissions.allow: Invalid permission rule "mcp__github__create_issue(repo:x)" was skipped: MCP rules do not support patterns in parentheses. ...
claude doctor did not flag Write(docs/**) or Bash(git * main). The docs say both produce a warning at session startup (unverified: we didn’t check the startup warning).
Adding working directories
Claude can read files inside the directory it was started in without asking. There are three ways to add more: --add-dir <path> at launch, /add-dir during a session, and additionalDirectories in settings. Added directories follow the same rules as the original one. Most of the .claude/ configuration inside an added directory is not loaded.
Source: https://code.claude.com/docs/en/permissions (checked 2026-10-20)
Settings files and precedence
| Precedence | Location | Scope |
|---|---|---|
| 1 (highest) | Managed settings | Deployed by your organization |
| 2 | --settings on the command line | That session only |
| 3 | .claude/settings.local.json | This project, just you |
| 4 | .claude/settings.json | This project, shared through Git |
| 5 | ~/.claude/settings.json | All your projects |
List keys such as permissions.allow merge across files instead of replacing each other. A deny applies from any level: an allow in user settings won’t override a deny in project settings. In a session, /permissions lists every rule and the file it came from. /status shows which settings files loaded, on the Setting sources line.
Source: https://code.claude.com/docs/en/settings and https://code.claude.com/docs/en/permissions (checked 2026-10-20)
Configs you can paste in
All three passed jq empty, and claude doctor reported no invalid settings.
Read-mostly and cautious (how plan mode works is in Claude Code Plan Mode: Plan Before It Edits):
{
"permissions": {
"defaultMode": "plan",
"allow": ["Bash(git status)", "Bash(git diff *)", "Bash(git log *)"],
"deny": ["Edit", "WebFetch"],
"additionalDirectories": ["../shared-docs"]
}
}
Tests and builds only:
{
"permissions": {
"allow": [
"Bash(npm run test:*)",
"Bash(npm run build)",
"Bash(npm run lint)",
"Edit(/src/**)",
"Edit(/tests/**)"
],
"ask": ["Bash(npm install *)", "Bash(git push *)"],
"deny": ["Bash(npm publish *)", "Bash(curl *)"]
}
}
Keep secret files out of reach:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Bash(printenv *)"
]
}
}
A command spelled differently, such as git -C . push, doesn’t match Bash(git push *). For anything that must never run, back the rule with a hook; see Claude Code Hooks in Practice. For handling secrets, see Keep Secrets Away from Claude Code.
Summary
- Rules are evaluated deny, then ask, then allow. An allow can’t carve an exception out of a deny.
defaultModehas six values.claude doctorcatches a misspelled one.- Put
*after the subcommand.:*only works at the end. - Only
ReadandEditrules apply to files./pathis relative to the settings file;//pathis absolute. - MCP rules with parentheses are skipped.
- Precedence runs managed,
--settings, local, project, user. Lists merge.