Claude Code permissions in settings.json: allow, deny and ask rules with real examples

Claude Code Published:

Control what Claude Code may do without asking: allow / deny / ask rules in settings.json, the pattern syntax, scope precedence, and recommended setups for solo and team work.

Verified on Sep 7, 2026 These tools change quickly. Please also check the latest official documentation.
Contents
  1. Where settings.json lives and which scope wins
  2. The shape of the permissions block
  3. Rule syntax
  4. Recommended setup: solo projects
  5. Recommended setup: teams
  6. Grants made during a session and /permissions
  7. Related settings
  8. Summary

By default, Claude Code asks before every file edit and every shell command. That is safe, but it kills your momentum. Granting everything, on the other hand, lets irreversible operations such as rm -rf or git push --force run without a second look.

This article shows how to use the permissions block in settings.json to draw the line: safe operations run automatically, dangerous ones are confirmed or blocked.

KEY POINT

What you will learn

  • Where settings.json lives and how the scopes override each other
  • How to write allow / deny / ask rules and the pattern syntax
  • Recommended setups for solo projects and for teams

Where settings.json lives and which scope wins

ScopePathPurposeIn Git
User~/.claude/settings.jsonDefaults for all your projectsNo
Project.claude/settings.jsonRules shared with the teamYes
Local.claude/settings.local.jsonPersonal additions; where "always allow" is savedNo
ManagedOS-specific policy locationRules enforced by an organization (Enterprise)-

When several files define the same key, managed settings win, then local, then project, then user. deny is always stronger than allow, so anything the team wants to forbid belongs in the project deny list: no personal allow can override it.

The shape of the permissions block

{
  "permissions": {
    "allow": [
      "Read",
      "Edit",
      "Bash(npm run lint)",
      "Bash(npm run test:*)",
      "Bash(git diff:*)",
      "Bash(git log:*)"
    ],
    "ask": [
      "Bash(git push:*)",
      "Bash(npm install:*)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)"
    ],
    "defaultMode": "acceptEdits"
  }
}
KeyMeaning
allowRules that run without confirmation
askRules that always prompt, even if an allow rule also matches
denyRules that are blocked. Highest priority
defaultModeWhat happens to operations that match no rule: default (prompt), acceptEdits (edits run, commands prompt), plan (read-only), bypassPermissions (everything runs; isolated environments only)

Rule syntax

A rule is ToolName(pattern). Omit the pattern to match the whole tool.

RuleMatches
Bash(npm run test:*)Any command starting with npm run test (:* is a prefix match)
Bash(git commit *)git commit followed by space-separated arguments
Read(./.env)Reading .env at the project root
Edit(src/**)Editing any file under src
WebFetch(domain:docs.example.com)Fetching from that domain
mcp__github__create_issueThe create_issue tool of the MCP server named github
mcp__githubEvery tool of that MCP server

Bash patterns match the command string

Bash(rm -rf:*) matches commands that begin with rm -rf. It does not match sudo rm -rf or cd /tmp && rm -rf. Treat deny rules as a safety net and protect files that truly matter with filesystem permissions or a container as well.

Edits run automatically; anything that touches the outside world is confirmed.

{
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm run *)",
      "Bash(npx tsc *)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(git add:*)",
      "Bash(git commit:*)"
    ],
    "ask": [
      "Bash(git push:*)",
      "Bash(npm install:*)",
      "Bash(npm uninstall:*)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(git reset --hard:*)"
    ]
  }
}

Keep only two things in the shared .claude/settings.json: deny rules everyone must respect, and allow rules that are safe no matter who runs them. Leave personal preferences to settings.local.json.

{
  "permissions": {
    "allow": [
      "Bash(pnpm lint)",
      "Bash(pnpm test:*)",
      "Bash(pnpm typecheck)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./infra/secrets/**)",
      "Edit(./.github/workflows/**)",
      "Bash(git push --force:*)",
      "Bash(git push -f:*)",
      "Bash(rm -rf:*)",
      "Bash(curl:*)",
      "Bash(wget:*)"
    ]
  }
}

curl and wget are denied to stop arbitrary downloads and data exfiltration. Move them to ask if you need them.

Grants made during a session and /permissions

When you choose "always allow" in a confirmation prompt, the rule is appended to the allow list of settings.local.json. These grants accumulate quietly, so review them with the /permissions command from time to time and remove what you no longer need.

If you are unsure which files are in effect, claude config list and /status show where the current settings were loaded from.

Permissions decide what runs; hooks let you enforce follow-up work such as running the linter after every edit, which makes wider automation safer. See Run lint and format automatically with Claude Code hooks. Keeping secrets out of the model's reach is covered in Stop AI coding tools from reading your API keys and secrets.

Summary

  • Settings override in the order user → project → local, and deny always wins
  • Write rules as Tool(pattern), for example Bash(npm run test:*)
  • Start with defaultMode: "acceptEdits" plus a short allow list, and put dangerous operations in deny
  • Review grants added during sessions with /permissions

FAQ

What is the difference between settings.json and settings.local.json?
settings.json is shared with the team through Git; settings.local.json is personal and ignored by Git. When both define the same key, the more specific scope wins.
What happens when a command matches both allow and deny?
deny wins. Put dangerous operations in deny and they will never run, even if an allow rule is written too broadly.
Where do the permissions I grant during a session go?
Choosing 'always allow' in the prompt appends the rule to .claude/settings.local.json. Use the /permissions command to review and edit them.

Questions & answers

Q. Can a deny rule for .env in Claude Code be overridden by an allow rule in settings.local.json?

Answer by the editorsFrequently searched question

No. deny wins over allow regardless of the scope it is written in (user, project or local). With this in the project .claude/settings.json, a broad Read allow in someone's settings.local.json still cannot read .env:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./.env.*)"]
  }
}

Note that deny rules govern Claude Code's tool calls. Reading through a shell command such as cat .env is a separate rule, and prefix matches like Bash(cat .env:*) are easy to sidestep. For values that truly matter, keep .env outside the workspace or inject secrets at run time from a secrets manager.

Primary sources

This article was drafted by AI from official documentation and reviewed by the site operator before publishing. Found a mistake? Let us know via the contact page.