Claude Code settings.json Deep Dive (Part 2): The Permissions System
Table of Contents
- Why permissions matter
- The full permissions object
- Rule format: two forms
- Three rule behaviors
- Wildcard syntax
- Legacy: prefix matching with prefix:*
- Modern: pattern wildcards with *
- Escape sequences
- MCP tool permission rules
- defaultMode: global fallback behavior
- additionalDirectories: expanding file access
- Rule sources and editability
- Practical configuration examples
- Summary
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.
Related Articles
Claude Code Agent Loop: Dissecting the Heart of an AI Coding Assistant
How does Claude Code understand your requests, invoke tools, and self-recover step by step? A source-code deep dive into the Agent Loop's core architecture — streaming responses, parallel tool execution, auto-compaction, and error recovery.
Claude Code settings.json Explained (1): Where Config Files Live and Who Wins
A complete guide to Claude Code's configuration file system — five config sources, their file paths, priority rules, array merging vs value overriding, and enterprise managed settings delivery.
Claude Code settings.json Deep Dive (Part 3): The Hooks System
A thorough breakdown of Claude Code's hooks configuration — four hook types, core events (PreToolUse/PostToolUse/Stop/Notification), stdin/stdout protocol, exit code semantics, and practical examples.
Claude Code settings.json Deep Dive (4): env, Models, Auth, and Other Useful Fields
A comprehensive guide to the remaining settings.json fields in Claude Code — env variable injection, model configuration, authentication helpers, Git attribution, session cleanup, language and UI, thinking depth, auto-updates, memory system, and more.
Claude Code /agents: Give Each Task Its Own Specialist AI
Create custom sub-agents for code exploration, architecture planning, and more — each with its own role, tools, and instructions.