Permissions

Claude Code permissions: stop the prompts without giving up control

How allow, ask and deny rules work in Claude Code, which settings file they go in, and which permission mode to use so it stops asking about everything.

Claude Code asks before it runs a shell command, edits a file or fetches a page. That's the right default on day one. By week two you're pressing Enter on npm test for the fortieth time and have stopped reading the prompts, so they no longer protect you. The fix is to tell Claude Code once what it may do, what it must ask about and what it may never do.

Allow, ask and deny

Permission rules come in three lists:

List What happens when a call matches
allow Runs without a prompt
ask Always prompts, even if an allow rule also matches
deny Refused

Claude Code checks deny first, then ask, then allow, and the first match wins. Specificity doesn't change the order, so a deny rule for Bash(aws *) blocks aws s3 ls even if you also allow Bash(aws s3 ls) exactly.

Type /permissions in a session to see every rule in force and which settings file it came from. You can add and remove rules there too.

Rule syntax

A rule is a tool name, optionally followed by a specifier in brackets.

Rule Matches
Bash Every shell command
Bash(npm run build) That exact command only
Bash(npm run *) npm run build, npm run test --watch and plain npm run
Read(.env) A .env file at any depth under the current directory
Edit(/src/**) Files under src/ in the project, when set in project settings
WebFetch(domain:example.com) Fetches from example.com
mcp__github__* Every tool from the MCP server named github

A few details decide whether a rule does what you meant:

  • Put the * after the subcommand. Bash(git log *) allows only git log, while Bash(git *) allows every git command, git push included.
  • The space matters. Bash(ls *) doesn't match lsof, but Bash(ls*) does.
  • Bash(ls:*) is an older way to write Bash(ls *) and still works.
  • Claude Code splits compound commands on &&, ||, ;, | and newlines, and each part has to match on its own. Allowing Bash(npm test *) doesn't approve npm test && curl ....
  • A leading / in a path rule anchors at the settings file's location, not the filesystem root. Use // for an absolute path and ~/ for your home directory.
  • Path rules only match Read and Edit. A rule like Write(docs/**) is accepted but never matches anything, so write Edit(docs/**).

Reading files inside your working directory never prompts, and a built-in set of read-only commands such as ls, cat, grep, find and read-only git runs without asking in every mode.

Where the rules live

Rules go under the permissions key of any settings file:

File Applies to Commit it?
~/.claude/settings.json You, in every project No
.claude/settings.json Everyone in the repository Yes
.claude/settings.local.json You, in this repository No
Managed settings Everyone your organisation deploys them to Set by an admin

The lists merge across files rather than replacing each other, so a project can add rules without wiping yours. A deny at any level beats an allow at any other level, and nothing overrides a managed deny, including command-line flags.

Pick "Yes, and don't ask again" on a prompt and Claude Code saves the rule to .claude/settings.local.json at the repository root. File-edit approvals are the exception: they last until the session ends.

Allow rules in a project's .claude/settings.json only take effect after you accept the workspace trust dialog for that folder, so a cloned repository can't pre-approve commands on your machine without you seeing them first. Deny and ask rules apply straight away, since they only restrict.

A starter config

This goes in .claude/settings.json for a typical Node project:

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

Routine build, test and commit work runs without a prompt, a push always asks, and Claude's file tools can't open your environment files. A Read deny also blocks Edit and Write on the same path.

Permission modes

Rules handle specific tools. The permission mode sets the baseline for everything else. Press Shift+Tab to cycle modes during a session, start with claude --permission-mode <mode>, or set permissions.defaultMode in a settings file.

Mode What runs without asking
default (shown as Manual) Reads only
acceptEdits Reads, file edits and common filesystem commands such as mkdir and mv
plan Reads, so Claude can explore and plan but doesn't edit your files
auto Everything, with a classifier checking each action first
dontAsk Reads and whatever your allow rules cover. Anything that would prompt is denied
bypassPermissions Everything, with no checks

In auto mode a separate classifier model reviews actions before they run and blocks ones that go beyond your request or look driven by hostile content Claude read. Since v2.1.283 it's the built-in starting mode for terminal and VS Code sessions, if your model and organisation support it. Your ask rules still prompt in auto mode, and the docs say plainly that it cuts prompts without guaranteeing safety.

dontAsk suits CI: claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read" never waits for input and refuses anything outside that list.

Bypass mode, and what it doesn't protect

--dangerously-skip-permissions (the same as --permission-mode bypassPermissions) skips the prompts and most safety checks, including writes to protected paths like .git and .claude. The docs say to use it only in isolated containers or VMs, and that it offers no protection against prompt injection. On Linux and macOS it refuses to start as root or under sudo.

A few things still hold in bypass mode. Deny rules block and ask rules prompt, as in every other mode, and Claude Code never auto-approves recognised rm and rmdir removals of critical paths such as rm -rf ~. Allow rules do nothing here, because everything is already allowed. To lock the mode out, set permissions.disableBypassPermissionsMode to "disable".

What I run

In my setup, ~/.claude/settings.json sets "defaultMode": "auto", and the allow list covers the tools I use all day: Bash(git:*), Bash(npm:*), Bash(node:*), Bash(jq:*). The deny list is short and specific. It holds Bash(eval:*) and matching Read and Edit denies for the few files that hold keys.

I don't rely on rules alone, because a Bash rule only matches the command text. Bash(rm *) stops rm -rf build/ but not /bin/rm -rf build/ or bash -c 'rm -rf build/', and a Read deny doesn't stop a Python script that opens the file itself. So I also run a PreToolUse hook on Bash, Read, Edit, Write, Grep and Glob calls. It exits with code 2, which blocks the call, on .env access, SSH and cloud credential paths, recursive force rm, and pipe-to-shell commands like curl ... | bash. A blocking hook stops the call before allow rules are checked, and in my sessions it keeps blocking with prompts bypassed: it blocked an rm in one of my sessions this week. My rules file records bypass mode as the normal way sessions run on the always-on VM I work from, with the hook layer as the guardrail instead of prompts. The hooks guide shows how to write one.

For enforcement that doesn't depend on command text at all, the built-in Bash sandbox restricts filesystem and network access at the OS level.

Questions

Why does Claude Code still ask after I chose "don't ask again"?

That choice saved an allow rule to your local settings, and an ask rule from the project or managed settings outranks it. Open /permissions to find the ask rule and the file it lives in.

Can I put permission rules in CLAUDE.md?

No. Claude Code enforces permissions itself, and CLAUDE.md only shapes what Claude tries to do. Use settings files, /permissions, a permission mode or a PreToolUse hook. See the memory guide for what CLAUDE.md is good for.

Is auto mode safe enough to leave on?

For everyday work in a repository you trust, it's a reasonable default, and it's now the built-in one. Add ask rules for actions you always want to see, such as Bash(git push *), and deny rules for secrets. For untrusted code or unattended runs, add a sandbox or container as well.

Sources

Field notes

Get the next guide by email.

Practical notes on getting more out of Claude Code: skills, hooks, MCP and agent workflows, plus what changes in each Overclaud release. Free, by email.

No spam. Unsubscribe any time. Privacy.