The short version
- Claude Code checks deny rules first, then ask, then allow, and the first match wins, so an allow rule can never carve an exception out of a deny rule.
- Pick a permission mode for the kind of work: plan to explore, acceptEdits to iterate on code you review, dontAsk with an exact allowlist for CI, and bypassPermissions only inside an isolated container or VM.
- A Bash deny rule such as Bash(git push *) stops the usual form of a command but not variants like git -C . push, so treat it as a guardrail, not a security boundary.
- Put shared rules in .claude/settings.json, personal ones in .claude/settings.local.json, and block secrets with Read deny rules; add the sandbox when you need OS-level enforcement.
On this page
- The short version
- How do Claude Code permission rules work?
- Where do the rules live?
- A starting configuration
- How do Bash wildcards match?
- Why a Bash deny rule is not a security boundary
- Which permission mode should you use?
- Gate subagents, MCP tools and parameters
- A practical setup checklist
- Conclusion
- Questions people ask
How do you set up Claude Code permissions so the agent can work without asking about every command, yet cannot touch what it should not? Write a short set of allow rules for the commands you trust, deny rules for what must never run, and pick a permission mode that fits the task. The part that trips people up is how the rules are matched, so this guide spends most of its time there.
Everything below comes from the Claude Code documentation on permissions, permission modes and settings. Check the docs for your installed version, because defaults change between releases.
How do Claude Code permission rules work?
A rule is either Tool or Tool(specifier). A bare tool name matches every use of the tool. A specifier narrows it:
| Rule | Effect |
|---|---|
Bash | All Bash commands |
Bash(npm run build) | Exactly that command |
Read(./.env) | Reading .env in the current directory |
WebFetch(domain:example.com) | Fetches to that host |
There are three kinds of rule: allow (run without asking), ask (always prompt) and deny (never run). They are evaluated in a fixed order: deny, then ask, then allow. The first match decides, and specificity does not change that.
This has a consequence people miss. A broad Bash(aws *) deny blocks every matching call, even if you also wrote a narrower Bash(aws s3 ls) allow. You cannot punch a hole in a deny. The same goes for ask over allow: a matching ask rule prompts even when a more specific allow also matches.
Deny also wins across files. If user settings allow something and project settings deny it, it is blocked.
Where do the rules live?
Settings are read from several places. Highest precedence first:
- Managed settings (set by your organization)
- Command line, such as
claude --settings .claude/settings.local.json(you, this project).claude/settings.json(everyone on the project, checked in)~/.claude/settings.json(you, every project)
Commit the rules the whole team should share to .claude/settings.json. Keep personal habits in the local file. You can inspect and edit the live rules with /permissions.
A starting configuration
This lets Claude run npm scripts and commit, while refusing a push:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)"
],
"deny": [
"Bash(git push *)"
]
}
}Add secrets protection with Read deny rules, which use gitignore-style patterns:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)"
]
}
}Use Edit(...) rules for edit restrictions rather than rules written for other tools, and confirm with /permissions that the rule behaves as you expect.
How do Bash wildcards match?
A * in a Bash rule matches any text, including spaces, and can sit anywhere in the rule. Four details decide whether your rule does what you think:
- The space before a trailing
*is part of the rule.Bash(ls *)matchesls -labut notlsof;Bash(ls*)matches both. Bash(npm run build)with no*matches only that exact command, sonpm run build --watchis not covered.- Everything before the first
*is matched as written.Bash(git log *)allows onlygit log, whileBash(git *)allows every git command. Avoid putting a*before the subcommand in an allow rule, as inBash(git * main). - Shell operators are understood.
Bash(safe-cmd *)does not permitsafe-cmd && other-cmd; each subcommand must match on its own.
The :* suffix is an equivalent way to write a trailing wildcard, so Bash(ls:*) equals Bash(ls *).
Why a Bash deny rule is not a security boundary
A Bash rule matches the command text Claude writes. It does not match the same program invoked another way. The documentation lists what slips past:
| Rule | Stops | Does not stop |
|---|---|---|
Bash(curl *) | curl https://example.com | /usr/bin/curl ..., sh -c 'curl ...' |
Bash(rm *) | rm -rf build/ | /bin/rm -rf build/, bash -c 'rm -rf build/' |
Bash(git push *) | git push origin main | git -C . push origin main |
Rules that try to constrain arguments are even more fragile. Bash(curl http://github.com/ *) will not match curl -X GET http://github.com/..., an https URL, a redirect, or a URL held in a variable.
Likewise, Read and Edit deny rules cover Claude's built-in file tools and recognised file commands such as cat and sed. They do not cover a Python or Node script that opens files itself. For enforcement that applies to every process, enable the sandbox, which restricts shell commands at the operating-system level. The docs recommend using permissions and the sandbox together.
If you need a hard block with custom logic, a PreToolUse hook is the next layer. A matching deny or ask rule still applies even if a hook returns "allow", so hooks cannot weaken your rules.
Which permission mode should you use?
Rules say what is allowed. The mode sets how much runs without a prompt. The documented modes:
| Mode | Runs without asking | Best for |
|---|---|---|
default (shown as Manual) | Reads only | Sensitive work, reviewing every action |
acceptEdits | Reads, file edits, common filesystem commands such as mkdir, touch, mv, cp | Iterating on code you are reviewing |
plan | Reads; edits blocked until you approve a plan | Exploring before changing anything |
auto | Actions reviewed by a background classifier instead of by you | Long tasks, fewer prompts |
dontAsk | Reads plus pre-approved tools; everything else denied | Locked-down CI and scripts |
bypassPermissions | Everything, with few exceptions | Isolated containers and VMs only |
Switch inside a session with Shift+Tab, or start with a flag:
claude --permission-mode planTo change the default for a project, set defaultMode in a settings file. On Claude Code v2.1.283 or later, auto mode is the built-in starting mode for interactive terminal and VS Code sessions; on earlier versions it is the starting mode only on Pro, Max and Team plans. So if you expect prompts and do not see them, check which mode you started in.
Plan mode for risky changes
Plan mode is the cheap way to find out whether Claude understood the task. Claude reads the code and proposes a plan, and edits wait until you approve it. Use it before refactors, migrations and anything touching several modules.
dontAsk for CI
In a pipeline nobody can answer a prompt. Pair dontAsk with an exact allowlist:
claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read"Anything outside the list is denied rather than hanging.
Gate subagents, MCP tools and parameters
Rules reach beyond Bash and files:
mcp__puppeteer__*matches every tool from one MCP server.Agent(Explore)orAgent(my-custom-agent)indenydisables a specific subagent.
A practical setup checklist
- Start a new project in
planor Manual mode and watch which prompts repeat. - Turn the repeats into narrow allow rules, such as
Bash(npm run *), and commit them in.claude/settings.json. - Add deny rules for secrets (
Read(./.env)) and for commands you never want, such asBash(git push *), remembering their limits. - Add
askrules for actions you want a human on, such as deploys. - Turn on the sandbox if the agent runs untrusted code or you need enforcement that does not depend on command text.
- Review the result with
/permissions.
Conclusion
Permissions work best as layers: narrow allow rules for speed, deny rules for guardrails, a mode matched to the task, and the sandbox or a hook where a hard guarantee matters. Remember the evaluation order, never rely on a Bash deny alone, and keep bypassPermissions for throwaway containers. If you run several agents at once, the same rules apply to each session, so a shared .claude/settings.json keeps them consistent.
Questions people ask
What is the order of allow, ask and deny rules in Claude Code?
Rules are evaluated deny first, then ask, then allow, and the first match decides. A narrower allow rule cannot override a broader deny rule, and a deny in one settings file wins over an allow in another.
Does a Bash deny rule like Bash(git push *) fully block pushes?
No. It stops the usual form but not variants such as git -C . push or running the program through sh -c. Treat it as a guardrail, and use the sandbox or a PreToolUse hook for stronger enforcement.
Which Claude Code permission mode should I use in CI?
dontAsk with an exact allowlist, for example --permission-mode dontAsk with --allowedTools "Bash(npm test)" "Read". Anything not on the list is denied instead of waiting for a prompt.
How do I stop Claude Code reading my .env file?
Add a Read deny rule such as Read(./.env) or Read(./secrets/**) to your settings. It covers Claude's file tools and recognised file commands, but not a script that opens files itself, so enable the sandbox for that.
Where should I put Claude Code permission rules for a team?
Put shared rules in .claude/settings.json and commit it. Keep personal preferences in .claude/settings.local.json or in your user settings in ~/.claude/settings.json.
Sources
Researched and drafted with Claude, then checked against the sources above. Atlas is an independent product, not affiliated with Anthropic or OpenAI. Claude and Claude Code are trademarks of Anthropic; Codex is a product of OpenAI.