#008

3 Things You Must Know About /permissions in Claude Code

Deny rules always win. Modes change everything. And the Tool(specifier) syntax supports wildcards and gitignore patterns.

The /permissions command looks like just another menu. In reality, it's the gateway to an access control system with modes, granular rules, and its own evaluation order. You don't need to master it all, but there are three concepts that will bite you if you don't know them.

TL;DR Deny rules always win. Modes change everything. And the Tool(specifier) syntax supports wildcards and gitignore patterns. That covers 90%.

Result:

/permissions

Allow rules:
  Bash(npm run *)         # from .claude/settings.json
  Bash(git commit *)      # from .claude/settings.json
  Edit(/src/**)           # from .claude/settings.json

Deny rules:
  Bash(git push *)        # from .claude/settings.json
  Read(.env)              # from .claude/settings.json

The 3 key concepts

1. Evaluation order: deny always wins

Rules are evaluated in this order: deny → ask → allow. First match wins. If a deny matches, it doesn't matter how many allow rules you have — the operation is blocked.

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

In this example, git commit -m "fix" passes. git push origin main is blocked. The deny for git push * matches before the allow for git * gets a chance.

2. Modes change all behavior

Claude Code has 5 permission modes. You can cycle through the three main ones with Shift+Tab during your session:

Mode What it does
default Prompts for permission on each new tool
acceptEdits Auto-accepts file edits
plan Read-only — Claude analyzes but modifies nothing
dontAsk Denies everything not explicitly in allow
bypassPermissions Skips all checks (isolated environments only)

dontAsk is the most misunderstood mode: it's not "accept everything without asking" — it's the exact opposite. It denies everything except what you've pre-approved via /permissions or permissions.allow.

3. The syntax supports more than you think

Rules follow the Tool(specifier) format with wildcards:

Rule What it allows
Bash(npm run *) Any npm script
Bash(* --version) Any command with --version
Edit(/src/**) Edit any file under src/ (recursive)
Read(.env) Read .env in the current directory
Read(~/.ssh/*) Read files in your .ssh directory
mcp__notion__* Any tool from the Notion MCP server

For Read and Edit, patterns follow the gitignore spec: * matches within a directory, ** matches recursively.

Where rules are stored

File Scope
~/.claude/settings.json Personal — all your projects
.claude/settings.json Project — shared with the team
.claude/settings.local.json Project — only you (gitignored)

Project rules take precedence over personal ones. If a project denies something you allow, the project wins.

Official docs: Configure permissions

Get only what matters

If I have nothing worth saying, you won't hear from me. When I do, you'll be the first to know. 7,000+ professionals already trust this.

Are you a professional Web developer?
No

Unsubscribe at any time.