Lesson 3.2
Permission Modes and Allow/Deny Rules
How Claude Code decides whether a tool call runs, asks or is blocked, and how to express that policy with modes and rules.
Every tool call in Claude Code passes through a permission check before it executes. The outcome is one of three: run it, ask the user, or block it. Two inputs decide that outcome: the current permission mode, which sets the default posture, and the rules in settings, which override the default for specific tools and patterns. The exam tests the modes, the rule syntax and the precedence between them.
Permission modes
| Mode | Behaviour |
|---|---|
default | Read-only tools run freely; edits and commands prompt unless a rule allows them |
acceptEdits | File edits in the project are auto-approved; commands still prompt |
plan | Claude may read and analyse but not edit or run commands; it produces a plan for review |
dontAsk | Anything not explicitly allowed by a rule is denied rather than prompted |
bypassPermissions | Nothing prompts; everything runs. Only for isolated environments |
Set the mode with --permission-mode <mode> on the command line, permissions.defaultMode in settings, or cycle through modes interactively with Shift+Tab. The --dangerously-skip-permissions flag is the command-line form of bypassPermissions and its name is a deliberate warning: it should only be used inside a sandbox or container with no credentials worth protecting. Enterprise administrators can set disableBypassPermissionsMode to make it unavailable.
dontAsk is the mode for unattended runs that must never hang on a prompt: combine it with a carefully built allow list, and anything unexpected fails fast instead of waiting for a human who is not there.
Rule syntax
Rules live under permissions in any settings file and come in three lists: allow, ask and deny. Each entry names a tool and, optionally, a pattern in parentheses.
{
"permissions": {
"allow": [
"Read",
"Bash(npm run lint)",
"Bash(npm run test:*)",
"Edit(src/**)",
"WebFetch(domain:docs.anthropic.com)",
"mcp__github__get_issue"
],
"ask": ["Bash(git push:*)", "mcp__slack"],
"deny": ["Read(./.env*)", "Bash(rm -rf:*)", "Edit(infra/**)"]
}
}
Pattern conventions:
- A bare tool name (
Read) matches every use of that tool. Bash(exact command)matches that command exactly;Bash(prefix:*)matches any command beginning with the prefix. Wildcards elsewhere in the string are also supported in current versions.Edit,WriteandReadtake gitignore-style path patterns; patterns are relative to the project root unless they begin with/(absolute) or~(home).WebFetch(domain:host)restricts web fetches to a domain.mcp__server__toolnames one MCP tool;mcp__servernames every tool on that server.
Precedence
deny wins over ask, and ask wins over allow. A tool call that matches a deny rule anywhere in the merged settings is blocked even if an allow rule from another scope matches too. Rules from all scopes are combined, so a user's personal allow list cannot defeat a project's deny list, and neither can defeat an enterprise managed deny. Mode is consulted only when no rule matches: an allow rule runs the tool even in default mode, and an ask rule prompts even in acceptEdits mode. Do not rely on deny rules to protect a bypassPermissions run; treat that mode as switching the permission system off and keep it out of shared environments.
Deny rules on Read and Edit are the mechanism for protecting secrets: denying Read(./.env*) prevents the model from ever seeing the file's contents, which is stronger than asking it not to look.
Working directories
By default Claude Code may read and edit within the directory it was launched in. permissions.additionalDirectories (or --add-dir) extends that boundary to other paths. Anything outside the permitted directories prompts, regardless of mode other than bypass.
Hooks and permissions
A PreToolUse hook runs after the rule check has decided to allow or ask, and can override by returning permissionDecision of allow, deny or ask. This makes hooks the place for policies that rules cannot express, such as "allow git commit only when the test suite passed earlier in this session". A PermissionRequest hook fires when a prompt would be shown and can answer on the user's behalf, which is how automated approval services integrate.
Practical policy design
Start from the actions that must never happen (deny), then the actions that need a human (ask), then the routine actions that should never interrupt (allow), and let the mode cover the rest. Review the merged result with /permissions, which shows the effective rules and their sources. For unattended agents prefer dontAsk with an explicit allow list over bypassPermissions; the former fails closed, the latter fails open.
Exam focus
- Modes:
defaultprompts for edits and commands,acceptEditsauto-approves project edits,planis read-only,dontAskdenies anything not allowed,bypassPermissionsruns everything. --dangerously-skip-permissionsequalsbypassPermissionsand belongs only in isolated sandboxes; enterprises can disable it.- Rule precedence is
deny>ask>allow; rules from all scopes are merged, so a project deny cannot be undone by a user allow. Bash(prefix:*)is prefix matching;Edit(path/**)uses gitignore-style globs;mcp__servercovers a whole MCP server.- Denying
Readon secret files stops the model seeing them, which is stronger than an instruction. - For unattended runs,
dontAskplus an allow list fails closed;bypassPermissionsfails open.
Check yourself
5 questions
1.A project's permissions contain an allow rule Bash(aws s3 ls) and a deny rule Bash(aws *). Claude attempts to run aws s3 ls. What happens?
2.Which permission mode lets Claude read files and run read-only commands to explore a codebase, but not edit source files?
3.A team adds the allow rule Bash(npm run test*) intending to approve the project's test scripts. During review an architect flags a problem. What is it?
4.What is the difference between a deny rule written as Bash and one written as Bash(rm *)?
5.To restrict network access, a team writes the allow rule Bash(curl https://github.com/ *) and expects curl to reach only GitHub. How should this be assessed?
