Claude Code permissions in settings.json: allow, deny and ask rules with real examples
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.
Contents
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
| Scope | Path | Purpose | In Git |
|---|---|---|---|
| User | ~/.claude/settings.json | Defaults for all your projects | No |
| Project | .claude/settings.json | Rules shared with the team | Yes |
| Local | .claude/settings.local.json | Personal additions; where "always allow" is saved | No |
| Managed | OS-specific policy location | Rules 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"
}
}
| Key | Meaning |
|---|---|
allow | Rules that run without confirmation |
ask | Rules that always prompt, even if an allow rule also matches |
deny | Rules that are blocked. Highest priority |
defaultMode | What 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.
| Rule | Matches |
|---|---|
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_issue | The create_issue tool of the MCP server named github |
mcp__github | Every 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.
Recommended setup: solo projects
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:*)"
]
}
}
Recommended setup: teams
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.
Related settings
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
denyalways wins - Write rules as
Tool(pattern), for exampleBash(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?
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.