PeakWatch · Guide

Claude Code permissions explained: allow rules, modes and auto mode

Claude Code permissions explained: modes, allow, deny and ask rule syntax, settings.json scopes, hooks as a gate, and when skipping prompts is sane.

By Hen Eytan11 min read4 sources
WRITE WANTS TO RUN src/relay/backoff.ts ALLOW DENY Deny, then ask, then allow. The first match wins, whateveris on the wrist

Key takeaways

  • Rules are evaluated deny, then ask, then allow; the first match wins and a deny from any file beats an allow from any other.
  • Rules are enforced by Claude Code, not the model: a CLAUDE.md instruction shapes behaviour, a rule changes what is allowed.
  • Put the wildcard after the subcommand: Bash(git log *) is safe, Bash(git *) allows every git command.
  • A Bash rule matches text, not programs; use sandboxing and PreToolUse hooks for boundaries that must hold.
  • Skip permissions only in an isolated container as a non-root user; on your own machine, write better rules and answer the rest from your phone or wrist.

Claude Code asks before it does most things that have side effects. That is the right default and the wrong permanent state: a session you supervise closely needs fewer prompts than the default gives, and a session you leave alone needs rules you have thought about. This guide explains how the permission system works, what the modes are, how to write allow, deny and ask rules, where they live, how hooks extend them, and when skipping permissions is and is not sane. It is checked against Anthropic's documentation as of October 2026.

How do Claude Code permissions work?

The permission system is tiered. In its manual mode (config value default), file reads and searches inside the working directory run without asking; shell commands, file edits, web fetches and web searches prompt you first, with a built-in set of read-only commands such as ls, cat, grep and read-only forms of git exempt. Each prompt shows what Claude is about to do and offers Yes, "Yes, and don't ask again", and No.

On top of the mode sit rules in three lists: allow, ask and deny. They are evaluated in that order reversed: deny first, then ask, then allow, and the first match decides. Specificity does not change the order, so Bash(aws *) in deny blocks aws s3 ls even if the narrower command is in allow. A deny from any settings file beats an allow from any other.

The principle the documentation states outright: "Permission rules are enforced by Claude Code, not by the model." Telling Claude in a prompt or a CLAUDE.md not to push to main shapes what it tries; only a rule, a mode or a hook changes what it is allowed to do.

What are the permission modes?

ModeWhat runs without askingBest for
default (Manual)Reads onlyReviewing every action yourself; sensitive work
acceptEditsReads, file edits, and common filesystem commands (mkdir, touch, mv, cp) inside the working directoryIterating on code you are reviewing as it lands
planReads; no edits until you approve a planExploring a codebase before changing it
autoEverything, with a background classifier checking shell commands and network requestsLong tasks; reducing prompt fatigue
dontAskReads in the working directory and pre-approved tools; anything that would prompt is deniedLocked-down CI and scripts
bypassPermissionsEverythingIsolated containers and VMs only

Press Shift+Tab in the terminal to cycle modes, or start with claude --permission-mode acceptEdits and the like. From version 2.1.283 auto mode is the built-in starting mode for interactive terminal and VS Code sessions; permissions.defaultMode in your user settings changes that. Rules layer on top of every mode: a deny blocks in all of them, including bypassPermissions, and an ask rule prompts in all of them, including auto.

How do allow, deny and ask rules work?

A rule is Tool or Tool(specifier). A bare tool name matches every use; as a deny it removes the tool from Claude's view entirely. The specifier narrows it, and its shape depends on the tool.

RuleMatchesNote
Bash(npm run build)Exactly npm run buildNo wildcard, exact command
Bash(git log *)git log and anything after itPut the * after the subcommand; Bash(git *) allows every git command
Read(./.env)Reading .env in the projectGitignore syntax; Read(.env) matches at any depth
Edit(/src/**)Edits under the project's src/One leading slash anchors at the settings file's directory, not the filesystem root; use // for absolute
WebFetch(domain:example.com)Fetches to that hostdomain:*.example.com for subdomains
mcp__github__get_*The get_ tools of the github MCP serverServer segment must be literal

A working file looks like this, and it is what the documentation gives as the example: npm scripts and commits run silently, pushes are refused.

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

Compound commands are split on &&, ||, ;, | and newlines, and an allow rule must match each part, so Bash(npm run *) does not approve npm run build && rm -rf /. A deny or ask applies if any part matches, including inside a subshell. Common wrappers such as timeout, nice and nohup are stripped before matching.

Where do the rules live? settings.json scopes

ScopeFileWho it affectsUse it for
Managedmanaged-settings.json, MDM, or the claude.ai consoleEveryone your organisation deploys it toSecurity policy; cannot be overridden below
User~/.claude/settings.jsonYou, in every projectYour own allow rules, default mode, theme
Shared project.claude/settings.jsonEveryone in the repository; commit itTeam permissions, hooks, plugins
Project local.claude/settings.local.jsonYou, in this project; kept out of gitPersonal overrides; where "don't ask again" saves

Precedence for an ordinary key runs managed, then --settings on the command line, then project local, then shared project, then user. Permission rules are simpler than that: a deny anywhere wins, then an ask anywhere, then an allow. The practical consequence is that a project's shared deny protects every teammate's local allow from accidentally opening it, and a managed deny cannot be undone by --allowedTools.

Use /permissions inside a session to see every rule and which file it came from. Choosing "Yes, and don't ask again" on a Bash or WebFetch prompt writes the rule to the repository's .claude/settings.local.json; a file-edit approval lasts only until the session ends.

What a Bash rule does not match

A Bash rule matches the text Claude writes, not the program it runs. The documentation is candid that a deny "isn't a security boundary around the program". Bash(rm *) stops rm -rf build/ and does not stop /bin/rm -rf build/ or bash -c 'rm -rf build/'; Bash(git push *) stops git push origin main and not git -C . push origin main.

Two things close that gap. Sandboxing gives OS-level filesystem and network enforcement that holds even if a prompt injection steers the model; the documentation calls permissions and sandboxing "complementary security layers". And for anything a pattern cannot express, a hook can read the whole command. The same instinct applies anywhere a string is as good as money: a voucher code, an API key in .env, a push to main. Guard the thing, not the spelling of the request for it.

Hooks as a gate

A PreToolUse hook is a shell command that runs before every tool call, before the permission prompt. It receives the tool name and input as JSON on stdin and can allow, deny or force a prompt. Exit code 2 blocks the call, with stderr as the reason, and nothing the hook prints can override that. Exit 0 with a JSON object on stdout gives a decision:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Pushes to main go through a pull request"
  }
}

Hooks live under the hooks key in any settings file, with a matcher such as "Bash" or "Edit|Write". Three properties make them a real gate rather than a suggestion: a blocking hook wins over an allow rule; a hook's "allow" does not override a deny or ask rule; and an rm aimed at a critical path such as your home directory is never approved by a hook or an allow rule in any mode.

The pattern the documentation suggests for a low-friction session is to allow Bash wholesale and register a hook that rejects the handful of commands you never want run.

When is skipping permissions sane, and when is it not?

--dangerously-skip-permissions starts a session in bypassPermissions mode: no prompts, no safety checks, including writes to protected paths such as .git and .claude. The documentation's instruction is unambiguous: "Only use this mode in isolated environments like containers, VMs, or dev containers without internet access, where Claude Code cannot damage your host system." It refuses to start as root or under sudo on Linux and macOS, and the first interactive use shows a dialog asking you to accept responsibility.

  • Sane: claude -p "<prompt>" --dangerously-skip-permissions inside a throwaway container or the sandbox runtime, running as a non-root user, on a checkout you can discard.
  • Sane: CI with an exact allowlist and no prompts: claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read". Anything else is denied, not asked.
  • Sane: auto mode on your laptop for a long task whose direction you trust. A separate classifier reviews shell commands and network requests and blocks anything that escalates beyond your request or looks driven by hostile content Claude read; your ask rules still prompt. The documentation adds that it "reduces permission prompts but does not guarantee safety".
  • Not sane: bypass mode on your own machine, with your credentials, your home directory and your network, because the prompts were annoying. The gate is the safety feature; the fix for annoying prompts is better rules and a cheaper way to answer the ones that remain.

Cloud sessions ignore bypassPermissions and dontAsk from checked-in settings, so a repository cannot start a cloud session in bypass mode.

Answering the prompts that remain

Once reads, tests, commits and your docs domains are allowed, the prompts left are the ones worth a human: a push, an install, a write outside the project, a command the rules do not describe. The question is how to answer them without sitting at the terminal. Anthropic's Remote Control forwards them to the Claude app on your phone and can push a notification when "actions required"; setting it up is a one-line command.

PeakWatch puts the same prompt on an Apple Watch or a Wear OS watch, with the tool, its arguments and the SDK's reason on screen, Allow, Deny and Say why one tap away, and an automatic deny after sixty seconds so an unanswered ask is never a yes. It never auto-approves; Always covers a tool for the rest of a session once you have decided it is safe. It is in private beta.

Together with the habits that make long sessions safe to leave, that is a permission setup you can walk away from.

Want this answered from your own data?

That is what PeakWatch does. Your coding agent, on your wrist. In private beta — the waitlist is free.

Join the beta About PeakWatch

Questions

What is the difference between allow, ask and deny in Claude Code?
Allow lets a matching tool call run without a prompt; ask forces a prompt even if an allow also matches; deny blocks it in every mode, including bypass. They are evaluated deny first, then ask, then allow, and the first match decides, regardless of which rule is more specific.
Where do I put Claude Code permissions in settings.json?
Under a permissions key with allow, ask and deny arrays. Use ~/.claude/settings.json for rules you want in every project, .claude/settings.json for rules the whole team should share and commit, and .claude/settings.local.json for personal overrides in one repository, which is where 'don't ask again' saves.
Is --dangerously-skip-permissions safe to use?
Only inside an isolated container, VM or sandbox runtime, as a non-root user, on work you could discard. It disables prompts and safety checks, including writes to protected paths. Claude Code refuses to run it as root and shows a warning the first time. On your own machine, use rules and auto mode instead.
What does Claude Code auto mode do?
It removes routine prompts and has a separate classifier model review shell commands and network requests before they run, blocking anything that escalates beyond your request, targets unrecognised infrastructure or looks driven by hostile content Claude read. Explicit ask rules still prompt. It reduces prompts; it does not guarantee safety.
How do I allow all git commands except push?
Add Bash(git *) to allow and Bash(git push *) to deny. Deny is evaluated first, so the push is refused even though the broad allow matches. Note that a rule matches text, so git -C . push is not caught; add a PreToolUse hook or sandboxing if the boundary must hold.
Can I approve Claude Code permissions from my phone or watch?
Yes. Remote Control forwards permission prompts to the Claude app and can push a notification when a decision is needed. PeakWatch shows the same request on an Apple Watch or Wear OS watch with the arguments visible, approves in one tap and denies automatically after sixty seconds. It is in private beta.

Sources

  1. Anthropic. Claude Code: configure permissions. https://code.claude.com/docs/en/permissions
  2. Anthropic. Claude Code: choose a permission mode. https://code.claude.com/docs/en/permission-modes
  3. Anthropic. Claude Code: settings files and precedence. https://code.claude.com/docs/en/settings
  4. Anthropic. Claude Code: hooks reference. https://code.claude.com/docs/en/hooks
Hen Eytan · Founder, UPeak

Builds the UPeak apps and writes up the research behind them.