Claude Code settings.json Deep Dive (Part 2): The Permissions System
Why permissions matter
In Part 1, we covered Claude Code’s five-layer configuration system — where the files live and which one wins. This post focuses on the permissions field, which controls what Claude is allowed to do, what it’s blocked from doing, and what requires your explicit approval.
Get it right, and Claude can work freely within the boundaries you set. Get it wrong, and you’re either buried in confirmation dialogs or you’ve given Claude more access than intended.
The full permissions object
{
"permissions": {
"allow": [],
"deny": [],
"ask": [],
"defaultMode": "default",
"additionalDirectories": []
}
}
Five fields, two categories:
- Rule arrays:
allow/deny/ask— each element is a permission rule string - Global settings:
defaultMode(default behavior) andadditionalDirectories(extra paths)
Rule format: two forms
Every permission rule is a string in one of these formats:
ToolName
ToolName(content)
ToolName: the tool name, capitalized — e.g.Bash,Write,Read(content): an optional content pattern inside parentheses — a command string or path glob
Example:
"allow": [
"Bash",
"Bash(npm install)",
"Bash(npm run:*)",
"Write(src/**)",
"mcp__github__create-pull-request"
]
"Bash" alone allows all Bash commands without restriction. "Bash(npm install)" allows only that exact command. "Bash(npm run:*)" uses a wildcard to allow any npm run variant.
Three rule behaviors
| Array | Effect |
|---|---|
allow | Always allowed — no confirmation dialog shown |
deny | Always blocked — Claude receives a rejection immediately |
ask | Always prompts for confirmation, regardless of defaultMode |
Priority: deny > allow > ask > defaultMode fallback.
Common pattern — allow specific commands, deny everything else:
{
"permissions": {
"allow": ["Bash(npm:*)", "Bash(git:*)"],
"deny": ["Bash"]
}
}
The allow entries carve out exceptions for npm and git commands. The deny entry catches everything else. The matching logic evaluates the most specific rule first — it’s not based on array order.
Wildcard syntax
Legacy: prefix matching with prefix:*
Bash(npm:*)
Bash(git:*)
Bash(docker:*)
Format: ToolName(prefix:*). Matches prefix or prefix <anything>, with a word boundary enforced — so npm:* will not accidentally match npmx.
This is the original wildcard syntax and still works today. It’s mainly useful for matching a CLI tool’s top-level command prefix.
Modern: pattern wildcards with *
Bash(npm * --save)
Bash(git * main)
Bash(* install)
Write(src/**/*.ts)
* matches any sequence of characters, including spaces. This form is more flexible — you can match the middle of a command, the tail, or multi-level directory paths.
Special behavior: if the pattern ends with * and contains only one wildcard, that trailing wildcard is optional. So Bash(git *) matches git add, git checkout, and the bare git command.
Escape sequences
If your command or path contains literal parentheses or asterisks, escape them:
| Character | Escaped form | Meaning |
|---|---|---|
( | \( | Literal open paren |
) | \) | Literal close paren |
\ | \\ | Literal backslash |
* | \* | Literal asterisk |
Example:
"allow": ["Bash(python -c \"print\\(1\\)\")"]
This rule matches the literal command python -c "print(1)".
MCP tool permission rules
MCP tools use a double-underscore format:
mcp__serverName
mcp__serverName__toolName
mcp__github: allows all tools from the GitHub MCP servermcp__github__create-pull-request: allows only that specific toolmcp__github__*: equivalent tomcp__github— explicit wildcard for all tools
Important: MCP rules do not support parenthesized content. The form mcp__server(pattern) is invalid.
defaultMode: global fallback behavior
When an operation doesn’t match any allow, deny, or ask rule, defaultMode determines what happens:
| Value | Behavior |
|---|---|
default | Show a confirmation dialog (the default) |
acceptEdits | Auto-approve file edits; still prompt for shell commands |
bypassPermissions | Skip all permission checks entirely (requires specific conditions to enable) |
dontAsk | Auto-approve all operations — no prompts shown |
plan | Plan-only mode — Claude can read but not write or execute |
Recommended combinations:
During development, acceptEdits lets Claude read and write files freely while still confirming shell commands. For CI or fully automated pipelines, combine explicit allow rules with dontAsk for hands-free operation.
additionalDirectories: expanding file access
By default, Claude Code can only access the current working directory and its subdirectories. additionalDirectories grants access to additional paths:
{
"permissions": {
"additionalDirectories": ["/Users/yourname/shared-libs", "/var/log/myapp"]
}
}
Common use cases: cross-package access in a monorepo, reading shared config files outside the project root, or inspecting system logs.
As covered in Part 1, arrays follow the merge rule: additionalDirectories entries from multiple config layers are concatenated, not overwritten.
Rule sources and editability
Permission rules can come from different configuration sources (see Part 1), but not all sources are editable:
| Source | Editable | Notes |
|---|---|---|
userSettings | Yes | ~/.claude/settings.json |
projectSettings | Yes | .claude/settings.json |
localSettings | Yes | .claude/settings.local.json |
policySettings | No | Enterprise admin policy — cannot be overridden |
flagSettings | No | CLI flags or env vars — read-only |
In enterprise environments, admins can lock down permission rules via policySettings. Individual user config cannot bypass these.
Practical configuration examples
Allow only npm and git, deny all other Bash:
{
"permissions": {
"allow": ["Bash(npm:*)", "Bash(git:*)"],
"deny": ["Bash"],
"defaultMode": "default"
}
}
Allow free file writes inside src/, prompt for anything outside:
{
"permissions": {
"allow": ["Write(src/**)"],
"defaultMode": "default"
}
}
Fully automated CI environment:
{
"permissions": {
"allow": ["Bash", "Write", "Read"],
"defaultMode": "dontAsk"
}
}
Always prompt before sudo:
{
"permissions": {
"ask": ["Bash(sudo *)"],
"allow": ["Bash(npm:*)", "Bash(git:*)"]
}
}
Summary
permissions is the most important field in Claude Code’s settings to get right. The core logic is straightforward:
- Use
allow/deny/askarrays to define your rules - Rules support exact match, prefix wildcards (
prefix:*), and pattern wildcards (*) - MCP tools use double-underscore format — no parenthesized content
defaultModehandles operations that don’t match any ruleadditionalDirectoriesextends file system access beyond the working directory
Next up: hooks — how to trigger your own shell scripts before and after Claude executes tools, enabling deeper automation and guardrails.
More Articles
- Why AI-First Startups Only Need One Programming Language
- cc-ping: Ping All Your Claude Code Configs in One Command
- Shocking! This Tool Lets Programmers Finish 95 Minutes of Work in 4 Minutes! 24x Efficiency Boost
- CCBot - 24x Development Efficiency Boost
- Claude Code /add-dir: The Monorepo Command You Miss
- Claude Code Token-Saving Tip: The Power of the Exclamation Mark
- I Built a Bot That Runs Claude Code From Chat
- Claude Code /btw Command Explained: Quick Side Questions Without Breaking Flow
- Claude Code /compact: Free Up Context, Keep Progress
- Claude Code /config: Every Setting Explained
- Claude Code /context: What's Eating Your Context Window?
- Claude Code /diff: See Exactly What Changed, Turn by Turn
- Claude Code /fast: Same Opus, 2x Speed — Worth It?
- Best Practice for External Knowledge in Claude Code: GitHub MCP + Context7
- Claude Code /hooks: Make AI Follow Your Rules
- Claude Code /init: Generate CLAUDE.md in 10 Seconds
- Claude Code MCP: Give Your AI Access to Any Tool
- Claude Code /memory Explained: Make AI Truly Remember Your Project
- Claude Code /model: Opus vs Sonnet vs Haiku Guide
- Claude Code /permissions: Fine-Grained Control Over What AI Can Do
- Claude Code /plan Explained: Think Before You Code
- Claude Code + Playwright MCP: AI Can Finally "See" the Page
- Claude Code /resume Command Explained: Don't Let Your Conversations Go to Waste
- Claude Code /review: Let AI Do Your Code Review
- Claude Code Skills Explained: Build Your Custom Command Library
- Claude Code /stats: See How Much AI Does For You
- Claude Code /status Command Explained: Your Session Dashboard
- Claude Code /tasks Command Explained: Master Your Background Tasks
- Claude Code /usage Command Explained: Know Your Remaining Quota
- Claude Code /vim: Vim Keybindings in Your AI Coding Assistant
- Claude Code in 2026: The Only Setup Guide You Need
- The Complete Guide to Claude: From Chat to Code to Automation
- Claude Code /agents Explained: Custom AI Sub-Agents, Each with Their Own Role
- Claude Code /doctor Explained: One-Click Diagnostics for Your Dev Environment
- Claude Code /effort Explained: Control How Hard Your AI Thinks
- Claude Code /cost Explained: How Much Is Your AI Coding Really Costing?
- Claude Code /export Explained: Take Your AI Conversations With You
- Claude Code /rewind Explained: AI Made a Mistake? Undo It Instantly
- Claude Code /plugin Explained: Install Plugins for Your AI Coding Assistant
- Claude Code /theme Explained: Give Your Terminal a New Look
- Claude Code /insights: Using AI to Analyze How You Use AI
- Claude Code /rename Explained: Give Your Sessions Meaningful Names
- Claude Code settings.json Explained (1): Where Config Files Live and Who Wins
- Claude Code settings.json Deep Dive (Part 3): The Hooks System
- Claude Code settings.json Deep Dive (4): env, Models, Auth, and Other Useful Fields