Claude Code permissions: modes, allow and deny rules explained
Claude Code asks before it edits a file, runs a command or fetches a page, unless a permission mode or a rule says it may go ahead. Press Shift+Tab to change the mode, and run /permissions to add rules such as "Bash(npm test *)" to allow, ask or deny. Deny rules win over everything else, in every mode.
Control
Take this page into your AI
Open in ChatGPT (new tab)Open in Claude (new tab)Claude Code reads files freely, but it stops and asks before it edits a file, runs a shell command or fetches a web page. 2 things decide when it stops asking: the permission mode, which sets the baseline for the whole session, and permission rules, which allow, ask about or deny particular commands, files and sites.
Permissions are enforced by Claude Code, not by the model. Writing "never push" in CLAUDE.md makes Claude less likely to try; a deny rule makes it impossible through that tool.
The 6 modes
Press Shift+Tab to move through the modes. The current one shows under the prompt.
| Mode | Runs without asking | Use it for |
|---|---|---|
Manual (default) | Reads only | Work you want to approve step by step |
Accept edits (acceptEdits) | Reads, file edits and simple file commands such as mkdir and mv | Building something while you review the diff |
Plan (plan) | Reads and read-only commands; no edits to your code | Looking around and agreeing a plan first |
Auto (auto) | Everything, with a second model checking each action first | Long tasks where prompts break your flow |
Don't ask (dontAsk) | Reads and what your rules allow; everything else is refused | Scripts and CI, where no one can answer |
Bypass (bypassPermissions) | Everything | Throwaway containers and VMs only |
Since version 2.1.283, auto mode is where an interactive session starts. In auto mode, a classifier reviews actions such as shell commands and network requests against what you asked for, and blocks risky ones like a force push or piping a downloaded script into a shell. To start somewhere else every time, set defaultMode in a settings file:
{
"permissions": {
"defaultMode": "acceptEdits"
}
}
Bypass is not in the Shift+Tab cycle unless you start with --permission-mode bypassPermissions or --dangerously-skip-permissions. It skips every prompt, even for writes to .git and .claude, so it belongs on a machine you can throw away.
Allow, ask and deny rules
Run /permissions to see every rule and the file it came from, and to add or remove one. Each rule is a tool name, with what it covers in brackets:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git diff *)",
"WebFetch(domain:docs.python.org)"
],
"ask": [
"Bash(git push *)"
],
"deny": [
"Read(./.env)",
"Read(./secrets/**)",
"Bash(curl *)"
]
}
}
- allow runs matching calls without a prompt.
- ask always prompts, even in modes that would not.
- deny refuses them. A deny rule naming a whole tool, such as
WebFetch, takes the tool away entirely.
Rules are checked in a fixed order: deny first, then ask, then allow. The first match decides, however specific a later rule is. Bash(aws *) in deny blocks aws s3 ls even when Bash(aws s3 ls) is in allow. The same holds across files: a deny in your own settings beats an allow in the project's, and the reverse.
Choosing Yes, and don't ask again on a prompt writes an allow rule for you, saved in .claude/settings.local.json at the root of the repository.
Writing Bash rules
A Bash rule matches the command text, and * stands for any text. Put the * after the part that says what the command does:
| Rule | Matches | Does not match |
|---|---|---|
Bash(npm test) | npm test | npm test --watch |
Bash(npm run *) | npm run build, npm run lint | npm install |
Bash(git log *) | git log, git log --oneline | git push |
Bash(git *) | every git command, push included | |
Bash(ls *) | ls, ls -la | lsof |
Bash(git *) is the common mistake: it allows git push --force along with git status. Write one rule per subcommand you trust.
Claude Code understands &&, |, ; and the other separators. Bash(npm test *) does not approve npm test && rm -rf dist, because each part has to match a rule on its own. A deny or ask rule catches its command anywhere in the line, including inside $(...) or a loop.
A small set of read-only commands, such as ls, cat, grep, head and wc, never needs a rule.
Files and web pages
File rules use the same patterns as .gitignore:
| Rule | Covers |
|---|---|
Read(./.env) | .env in the current folder |
Read(**/.env*) | every .env file, at any depth |
Edit(/src/**/*.ts) | TypeScript files under src/ from the project root |
Read(~/.aws/**) | your AWS folder in your home directory |
Read(//etc/**) | an absolute path: note the 2 slashes |
A Read deny also stops Claude writing or editing that path. A .claudeignore file does nothing; move its lines into Read deny rules.
Web pages are matched by host: WebFetch(domain:example.com) covers that site, and WebFetch(domain:*.example.com) its subdomains. MCP tools are matched by server: mcp__github covers every tool of the server named github.
What rules cannot stop
A rule matches the command Claude usually writes, not every way to run the same program. Bash(curl *) in deny stops curl https://example.com but not /usr/bin/curl https://example.com or sh -c 'curl ...'. Likewise, a Read deny stops cat .env but not a Python script that opens .env itself.
For a limit that holds whatever the command looks like, turn on the sandbox with /sandbox (macOS, Linux and WSL2). It restricts what shell commands can read, write and reach on the network at the operating system level, so even a command smuggled in by Prompt InjectionPrompt InjectionAn attack that hides instructions in text a model reads, such as a web page, an email or a file, so the model follows the attacker instead of you. cannot leave its fence. With the sandbox on, commands inside it can run without prompts, and your deny and ask rules still apply.
For your own logic, such as refusing any command that touches production, a PreToolUse hook sees every call before it runs.
Folders outside the project
Claude works in the folder you started it in, where reads need no prompt. To give it another folder on the same terms, add it with --add-dir ../shared-lib at start, /add-dir during a session, or additionalDirectories in settings.
Rules a repository brings with it
A project's .claude/settings.json can contain allow rules. Since they grant access, Claude Code applies them only after you accept the trust dialog for that folder, which lists them first. Deny and ask rules apply at once, because they only take access away. Read the list before you trust a repository you did not write.
Questions
- How do I stop Claude Code asking for permission all the time?
- Allow the commands you trust instead of turning prompts off. Run /permissions and add allow rules such as Bash(npm run *) and Bash(git diff *), or choose "Yes, and don't ask again" on a prompt. Auto mode, where a second model checks each action, also removes most prompts. Keep bypassPermissions for throwaway containers.
- Is it safe to use --dangerously-skip-permissions?
- Only on a machine you can lose, such as a container or VM with no keys or production access. It skips every prompt, including writes to protected folders such as .git and .claude. Deny rules and a few safeguards still apply, but nothing else stands between Claude and your files.
- Does a deny rule in CLAUDE.md stop Claude Code?
- No. CLAUDE.md shapes what Claude tries to do; it does not change what Claude Code allows. Permissions are enforced by Claude Code from the rules in settings files, the permission mode and hooks.
- Where does "Yes, and don't ask again" save the rule?
- In .claude/settings.local.json at the root of the Git repository, so it applies to later sessions anywhere in that repository, worktrees included. That file is yours and is not meant to be committed.
- Can a Bash deny rule stop a command for good?
- Not on its own. A rule matches the command as Claude writes it, so Bash(rm *) stops rm -rf build/ but not /bin/rm -rf build/ or bash -c 'rm -rf build/'. For a hard limit on files and network, turn on the sandbox with /sandbox.
Sources