Claude Code permissions settings: rules, scopes, safe defaults

Configure Claude Code permissions in settings.json: allow, ask and deny rule syntax, which scope wins, permission modes, and a safe starter config.

Claude Code permissions settings decide which tool calls run without asking, which prompt you, and which are refused. This guide covers the rule syntax, which settings file wins, the permission modes, and a safe starter settings.json. Everything below is checked against the official permissions page and settings page on 2026-10-10. Version numbers are quoted where the docs give them; confirm against the current pages.

How Claude Code permissions work

The permission system is tiered by tool type, as shown for Manual mode:

Tool type Approval required “Yes, and don’t ask again”
Read-only (file reads, Grep) No, inside the working directory and additional directories N/A
Bash commands Yes, except a built-in set of read-only commands Permanent, per repository and command
File modification Yes Until the session ends
WebFetch Yes, except preapproved documentation domains Permanent, per repository and domain
WebSearch Yes Permanent, per repository

When an approval is saved permanently, Claude Code writes the rule to .claude/settings.local.json at the git repository root. Since v2.1.211 it resolves worktrees to the main checkout, so the rule applies to subdirectories and worktrees too. Before that version it saved the rule in the starting directory. A file-edit approval is never written to a file.

One point worth stating plainly: the docs note that rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape what Claude tries, but they do not change what Claude Code allows.

Where permission settings live and which scope wins

Permissions use the same files as every other setting. The settings page lists them:

Scope File Use it for
User ~/.claude/settings.json Your own rules, in every project
Shared project .claude/settings.json Team rules; commit it
Project local .claude/settings.local.json Personal overrides for one project; add it to .gitignore yourself if you create it by hand
Managed managed-settings.json or other managed sources Organization policy

For ordinary settings, precedence runs from highest to lowest: managed, command line (--settings), project local, shared project, user. List keys such as permissions.allow are merged across files rather than replaced.

For permission rules the permissions page adds the rule that matters most: if a tool is denied at any level, no other level can allow it. A user-level deny blocks a project-level allow, and a managed deny cannot be overridden by --allowedTools.

One trap from the troubleshooting section: a rule saved by “Yes, and don’t ask again” lands in your local file, and an allow there does not outrank an ask rule from a project or managed file.

Rule syntax: allow, ask, deny, specifiers and wildcards

A rule is Tool or Tool(specifier). The three lists behave as follows, per Manage permissions:

  • allow runs the tool without manual approval.
  • ask prompts every time.
  • deny refuses.

Evaluation order is deny, then ask, then allow, and the first match decides. Specificity does not change that order. A broad Bash(aws *) deny blocks calls even if Bash(aws s3 ls) is in allow.

A bare tool name (Bash, WebFetch, Read) matches every use. As a deny rule, a bare name also removes the tool from Claude’s context entirely. A scoped deny such as Bash(rm *) leaves the tool available and blocks matching calls.

The wildcard rules for Bash are small but easy to get wrong:

You write Matches Doesn’t match
Bash(npm run build) npm run build npm run build --watch
Bash(npm run *) npm run build, npm run test --watch, npm run npm install
Bash(ls *) ls -la, ls lsof
Bash(ls*) ls -la, lsof

Put the * after the subcommand. Bash(git log *) allows only git log, while Bash(git *) allows every git command. Claude Code warns at startup about an allow rule with a * before the subcommand, such as Bash(git * main). The space before a trailing * is part of the rule, and Bash(ls:*) is an equivalent older-style way to write Bash(ls *).

Tool-specific rules: Bash, Read and Edit, WebFetch, MCP, subagents

Bash

Claude Code knows shell operators: Bash(safe-cmd *) does not permit safe-cmd && other-cmd, because each subcommand must match on its own. It also strips a fixed set of wrappers (timeout, time, nice, nohup, stdbuf, among others) before matching, so Bash(npm test *) matches timeout 30 npm test. Runners such as npx and docker exec are not stripped.

The limit that matters for security: a Bash rule matches the command text Claude writes, so it is not a boundary around a program. These deny rules stop the first form only:

Rule Stops Doesn’t stop
Bash(curl *) curl https://example.com /usr/bin/curl https://example.com, sh -c 'curl https://example.com'
Bash(git push *) git push origin main git -C . push origin main

For enforcement that does not depend on command text, the docs recommend sandboxing or a PreToolUse hook. Argument-constraining patterns such as Bash(curl http://github.com/ *) are called fragile in the docs.

Read and Edit

Use Read(path) and Edit(path), which follow gitignore pattern syntax. Edit rules apply to all built-in file-editing tools; the docs say Claude makes a best-effort attempt to apply Read rules to tools like Grep and Glob. A path rule written for Write is accepted but never consulted, so use Edit(docs/**).

Four path prefixes:

Pattern Meaning
//path Absolute path from the filesystem root
~/path From your home directory
/path Relative to the settings source, not the filesystem root
path or ./path Relative to the current directory

/Users/alice/file is not an absolute path; use //Users/alice/file. In user settings, /path resolves under ~/.claude, so Read(/secrets/**) there does not block a secrets directory in your project.

Allow and deny differ for single-segment directory patterns: Edit(src/**) as an allow rule matches only <cwd>/src, but as a deny or ask rule it matches a src directory at any depth. Use Edit(**/src/**) to allow at any depth.

Read and Edit deny rules cover built-in file tools, recognized Bash file commands such as cat and sed, and Bash redirection targets. They do not cover subprocesses that read files themselves, such as a Python or Node script. A .claudeignore file has no effect; move its entries into Read deny rules.

WebFetch

WebFetch rules use domain:. WebFetch(domain:*.example.com) matches subdomains at any depth but not example.com itself. Wildcards need Claude Code v2.1.172 or later. WebFetch rules do not stop network access from Bash: if Bash is allowed, curl can still reach any URL.

MCP and subagents

MCP rules use the server name: mcp__puppeteer matches every tool from that server, and mcp__puppeteer__puppeteer_navigate matches one tool. Allow globs are only accepted after a literal mcp__<server>__ prefix. A bare mcp__* allow rule is skipped with a warning.

Subagents use Agent(Name); add them to deny to disable one, for example Agent(Explore).

Permission modes and managing rules with /permissions

Run /permissions to see every rule and the settings file it comes from. Changes apply from Claude’s next tool call, even in the middle of a turn (v2.1.234 and later).

Set the starting mode with defaultMode in a settings file. The documented modes are:

Mode Behaviour
default Prompts on first use of each tool (labeled Manual in the CLI)
acceptEdits Auto-accepts file edits and common filesystem commands such as mkdir and mv in the working directory
plan Read and explore without editing source files
auto A background classifier checks actions instead of prompting you
dontAsk Auto-denies anything that would prompt
bypassPermissions Skips prompts, including for .git and .claude; use only in isolated containers or VMs

Two caveats from the settings troubleshooting section: defaultMode values auto and bypassPermissions do not take effect from project or local settings. To block them, set permissions.disableBypassPermissionsMode or permissions.disableAutoMode to "disable"; this is most useful in managed settings.

Workspace trust and project allow rules

permissions.allow rules and permissions.additionalDirectories in a project’s .claude/settings.json grant capability, so Claude Code applies them only after you accept the workspace trust dialog. deny and ask rules are not gated.

Interactive sessions only show that dialog. A claude -p or SDK run never shows it, and the docs say project allow rules are not used there; Claude Code prints a this workspace has not been trusted warning to stderr. If you script claude -p in CI, do not rely on a committed allow list.

A safe starter settings.json

This is our own starting point, assembled from rule shapes in the docs above, not an official sample. Put it in .claude/settings.json and adjust the commands to your project:

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

What each part does, per the docs:

  • Bash(npm run *) and Bash(git commit *) follow the wildcard rule: the * comes after the subcommand.
  • git push prompts instead of running. As a Bash rule it does not match git -C . push.
  • The Read deny rules block the file tools from .env and secrets/. Deny rules also stop the Edit and Write tools on the same path (v2.1.208 and later for edits, v2.1.228 for writes). A script that opens the file itself is not covered.

The permissions page also has an Example configurations section that points to a repository of example settings.

What to change (checklist)

  1. Run /permissions and read which file each existing rule comes from. Remove saved “don’t ask again” rules you do not recognize in .claude/settings.local.json.
  2. Move team rules into the committed .claude/settings.json, and personal ones into ~/.claude/settings.json.
  3. Put secrets paths in Read deny rules. Add an Edit deny rule for paths no tool may change.
  4. Write Bash allow rules with the * after the subcommand, and use ask for commands with side effects such as git push.
  5. Our inference: treat Bash deny rules as a speed bump, and add the sandbox or a hook where a block must hold. The docs state that rules alone are not a boundary around a program.
  6. Ask your admin to set disableBypassPermissionsMode in managed settings if bypass should be off.
  7. Remember CI: committed allow rules need workspace trust, which claude -p never shows.

If you are also extending Claude Code with reusable instructions, see what Claude Code skills are; a skill’s allowed-tools field is a separate pre-approval mechanism covered on the skills docs.

FAQ

Which wins in Claude Code, an allow rule or a deny rule?
Deny. Rules are evaluated in the order deny, ask, allow, and the first match decides. A narrower allow rule cannot carve an exception out of a broader deny rule, and a deny at any settings level cannot be overridden by another level. See [Manage permissions](https://code.claude.com/docs/en/permissions#manage-permissions) and [Settings precedence](https://code.claude.com/docs/en/permissions#settings-precedence).
Where does "Yes, and don't ask again" save my choice?
For a Bash command or a WebFetch domain, Claude Code writes an allow rule to .claude/settings.local.json at the root of the git repository (v2.1.211 and later). A file-edit approval is not saved; it lasts until the session ends. Details in [Permission system](https://code.claude.com/docs/en/permissions#permission-system).
Do Bash deny rules fully block a program?
No. A rule such as Bash(curl *) stops the usual form of the command but not /usr/bin/curl or sh -c 'curl ...'. The docs say a Bash rule is not a security boundary and point to sandboxing or a PreToolUse hook for enforcement. See [What a Bash rule doesn't match](https://code.claude.com/docs/en/permissions#bash-rule-limits).
Why are the allow rules in my committed .claude/settings.json ignored?
Project allow rules and additionalDirectories only apply after you accept the workspace trust dialog for that folder. Deny and ask rules are not gated, since they only restrict. See [Project allow rules and workspace trust](https://code.claude.com/docs/en/permissions#project-allow-rules-and-workspace-trust).