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

- Page: https://tecyfy.com/coding-agents/claude-code/permissions
- Guide: Claude Code (https://tecyfy.com/coding-agents/claude-code), from Anthropic's documentation (https://code.claude.com/docs/en/overview)

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](/coding-agents/claude-code/project-instructions) 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](/coding-agents/claude-code/settings):

```json
{
  "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:

```json
{
  "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](/coding-agents/claude-code/mcp) 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 Injection](https://tecyfy.com/concepts/prompt-injection) 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](/coding-agents/claude-code/hooks) 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

- [Configure permissions](https://code.claude.com/docs/en/permissions)
- [Choose a permission mode](https://code.claude.com/docs/en/permission-modes)
- [Sandboxing](https://code.claude.com/docs/en/sandboxing)
