Atlas
TechnicalHow-to

Claude Code Permissions: Allow, Deny Rules and Modes

Set up Claude Code permissions that hold: how allow, ask and deny rules are ordered, which mode to pick, and the Bash rule traps that let commands slip through.

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
  1. The short version
  2. How do Claude Code permission rules work?
  3. Where do the rules live?
  4. A starting configuration
  5. How do Bash wildcards match?
  6. Why a Bash deny rule is not a security boundary
  7. Which permission mode should you use?
  8. Gate subagents, MCP tools and parameters
  9. A practical setup checklist
  10. Conclusion
  11. 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:

RuleEffect
BashAll 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:

  1. Managed settings (set by your organization)
  2. Command line, such as claude --settings
  3. .claude/settings.local.json (you, this project)
  4. .claude/settings.json (everyone on the project, checked in)
  5. ~/.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:

json
{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)"
    ]
  }
}

Add secrets protection with Read deny rules, which use gitignore-style patterns:

json
{
  "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 *) matches ls -la but not lsof; Bash(ls*) matches both.
  • Bash(npm run build) with no * matches only that exact command, so npm run build --watch is not covered.
  • Everything before the first * is matched as written. Bash(git log *) allows only git log, while Bash(git *) allows every git command. Avoid putting a * before the subcommand in an allow rule, as in Bash(git * main).
  • Shell operators are understood. Bash(safe-cmd *) does not permit safe-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:

RuleStopsDoes 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 maingit -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:

ModeRuns without askingBest for
default (shown as Manual)Reads onlySensitive work, reviewing every action
acceptEditsReads, file edits, common filesystem commands such as mkdir, touch, mv, cpIterating on code you are reviewing
planReads; edits blocked until you approve a planExploring before changing anything
autoActions reviewed by a background classifier instead of by youLong tasks, fewer prompts
dontAskReads plus pre-approved tools; everything else deniedLocked-down CI and scripts
bypassPermissionsEverything, with few exceptionsIsolated containers and VMs only

Switch inside a session with Shift+Tab, or start with a flag:

bash
claude --permission-mode plan

To 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:

bash
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) or Agent(my-custom-agent) in deny disables a specific subagent.

A practical setup checklist

  1. Start a new project in plan or Manual mode and watch which prompts repeat.
  2. Turn the repeats into narrow allow rules, such as Bash(npm run *), and commit them in .claude/settings.json.
  3. Add deny rules for secrets (Read(./.env)) and for commands you never want, such as Bash(git push *), remembering their limits.
  4. Add ask rules for actions you want a human on, such as deploys.
  5. Turn on the sandbox if the agent runs untrusted code or you need enforcement that does not depend on command text.
  6. 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

  1. Configure permissions - Claude Code Docs
  2. Choose a permission mode - Claude Code Docs
  3. Settings files and precedence - Claude Code Docs

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.