Complete Guide to Claude Code Permissions | settings.json, Hooks & Allowlist Explained
A complete guide to Claude Code permissions. Learn allow/deny/ask, automation with Hooks, environment-specific settings.json, and practical patterns—all with working code.
Claude Code has extremely powerful file operation and command execution capabilities. Permissions are what allow you to safely control that power. Let’s move beyond “using it without really thinking” and design a Claude Code that works exactly as intended.
This article provides a thorough, working-code walkthrough of every setting in .claude/settings.json, Hook implementation patterns, and environment-specific permission design.
Overview of Permissions
Claude Code permissions are controlled at 3 levels.
| Level | Key | Behavior |
|---|---|---|
| Allow | allow | Executes automatically without a confirmation dialog |
| Ask | ask | Requires user approval each time |
| Deny | deny | Cannot be executed at all (blocked with an error) |
Settings are written in .claude/settings.json. Placing it at the project root lets the team share it via git, while placing it in ~/.claude.json makes it a global setting.
Priority (highest first):
Project .claude/settings.json
> Global ~/.claude.json
> Default (everything is ask)
Basic Structure of settings.json
{
"permissions": {
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)",
"Bash(npm run *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push --force*)"
],
"ask": [
"Write(**)",
"Edit(**)",
"Bash(git commit*)"
]
},
"hooks": {
"PreToolUse": [],
"PostToolUse": []
}
}
Tool Names and Pattern Syntax
Permissions are written in the format “ToolName(argument pattern)”.
Main Tool List
| Tool Name | Description |
|---|---|
Read | File reading |
Write | Creating new files |
Edit | Partial modification of existing files |
Bash | Shell command execution |
Glob | File pattern search |
Grep | Content search |
WebFetch | URL fetching |
Agent | Sub-agent launch |
Pattern Syntax
"Read(**)" // Allow reading all files
"Read(src/**)" // Allow only under src/
"Read(*.md)" // Allow only .md files
"Bash(npm run *)" // Allow only commands starting with npm run
"Bash(git *)" // Allow all git commands
"Bash(rm -rf *)" // Deny rm -rf
** matches all paths including directories; * matches a single segment.
Practical Patterns
Pattern 1: Solo Development (relatively permissive)
{
"permissions": {
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)",
"Bash(npm *)",
"Bash(git log*)",
"Bash(git diff*)",
"Bash(git status*)",
"Bash(git add*)",
"Bash(node *)",
"Bash(echo *)",
"Bash(cat *)",
"Bash(ls *)"
],
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~*)",
"Bash(git push --force *main*)",
"Bash(git push --force *master*)"
],
"ask": [
"Write(**)",
"Edit(**)",
"Bash(git commit*)",
"Bash(git push*)",
"Bash(rm *)"
]
}
}
Pattern 2: Team Development (security-focused)
{
"permissions": {
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)",
"Bash(npm run lint)",
"Bash(npm run test)",
"Bash(npm run typecheck)",
"Bash(git log*)",
"Bash(git diff*)",
"Bash(git status*)",
"Bash(git branch*)"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Bash(git push -f*)",
"Bash(git reset --hard*)",
"Bash(git rebase *main*)",
"Bash(git rebase *master*)",
"Bash(DROP *)",
"Bash(TRUNCATE *)",
"Bash(curl * | bash)",
"Bash(wget * | sh)"
],
"ask": [
"Write(**)",
"Edit(**)",
"Bash(git commit*)",
"Bash(git push*)",
"Bash(git add*)",
"Bash(npm install*)",
"Bash(*deploy*)"
]
}
}
Pattern 3: Production Environment (read-only)
{
"permissions": {
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)",
"Bash(git log*)",
"Bash(git diff*)",
"Bash(git status*)",
"Bash(git show*)",
"Bash(cat *)",
"Bash(ls *)",
"Bash(ps *)",
"Bash(df *)",
"Bash(top *)"
],
"deny": [
"Write(**)",
"Edit(**)",
"Bash(git push*)",
"Bash(git commit*)",
"Bash(git reset*)",
"Bash(rm *)",
"Bash(mv *)",
"Bash(*deploy*)",
"Bash(*restart*)",
"Bash(*kill *)"
],
"ask": []
}
}
In production, specify this with CLAUDE_SETTINGS=.claude/settings.production.json claude.
Pattern 4: Content Generation Only (the pattern used on this site)
{
"permissions": {
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)",
"Write(site/src/content/**)",
"Write(content/**)",
"Edit(site/src/content/**)",
"Edit(content/**)",
"Bash(git log*)",
"Bash(git diff*)",
"Bash(git status*)",
"Bash(node scripts/*)",
"Bash(QIITA_TOKEN=* node scripts/qiita-publish.mjs)"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Edit(.env*)",
"Read(.env*)"
],
"ask": [
"Bash(git add*)",
"Bash(git commit*)",
"Bash(git push*)",
"Bash(bash scripts/deploy.sh*)"
]
}
}
The key is restricting writes to a specific directory like Write(site/src/content/**).
Hooks: Running Processes Before and After Permissions
Hooks are a mechanism that automatically runs commands before and after tool execution. They can be used for security checks and auto-formatting.
PreToolUse: Pre-execution Hook
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git add*)",
"hooks": [{
"type": "command",
"command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 .env addition detected!' && exit 1 || exit 0"
}]
},
{
"matcher": "Bash(git commit*)",
"hooks": [{
"type": "command",
"command": "node scripts/secret-scan.mjs"
}]
},
{
"matcher": "Bash(rm*)",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Delete command detected. Executing in 5 seconds. Press Ctrl+C to abort.' && sleep 5"
}]
}
]
}
}
If a hook command returns exit code 1, the tool execution is blocked. This is the most important point.
PostToolUse: Post-execution Hook
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -20 || true"
}]
},
{
"matcher": "Bash(git commit*)",
"hooks": [{
"type": "command",
"command": "git log --oneline -3"
}]
}
]
}
}
PostToolUse is used for post-execution checks and side effects—for example, automatically running type checks after file edits or showing the latest 3 log entries after a commit.
Practical Hooks Recipe Collection
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(npm install*)",
"hooks": [{
"type": "command",
"command": "echo '📦 Adding package. Please check package.json.'"
}]
},
{
"matcher": "Bash(*deploy*)",
"hooks": [{
"type": "command",
"command": "read -p '🚀 About to deploy. Continue? [y/N] ' ans && [ \"$ans\" = 'y' ] || exit 1"
}]
}
],
"PostToolUse": [
{
"matcher": "Write(*.ts)|Edit(*.ts)",
"hooks": [{
"type": "command",
"command": "npx eslint --fix $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null || true"
}]
}
]
}
}
Permission Modes: Permission Level at Launch
You can also specify the mode when launching the claude command.
# Normal mode (follows settings.json)
claude
# Auto-approve all operations (dangerous! only for trusted environments)
claude --dangerously-skip-permissions
# Skip only specific operations
claude --allowedTools "Read,Grep,Glob"
# Non-interactive mode (used in CI/CD)
claude -p "Run tests and report results" --dangerously-skip-permissions
--dangerously-skip-permissions should only be used for CI/CD automation or fully understood automation scripts, and avoided in daily interactive use.
Configuration File Priority and Overrides
When multiple configuration files exist:
~/.claude.json ← Global (shared across all projects)
+
.claude/settings.json ← Project (git-managed)
+
.claude/settings.local.json ← Personal overrides (recommend gitignore)
=
Merged settings are applied
Write personal additional settings in .claude/settings.local.json and add it to gitignore. To prevent team deny lists from being overridden by personal settings, the safe design is to only write deny rules in settings.json.
# Add to .gitignore
.claude/settings.local.json
Top 5 Pitfalls
1. Getting wildcard patterns wrong
// ❌ This matches only the single command "git"
"Bash(git)"
// ✅ Matches git followed by arguments too
"Bash(git *)"
"Bash(git*)" // Also works without space, but explicit * is safer
2. Forgetting that deny takes priority over ask
// With this config, Bash(rm -rf /tmp/test) is caught by deny and blocked
// It never reaches ask
{
"deny": ["Bash(rm -rf*)"],
"ask": ["Bash(rm*)"] // ← rm -rf is handled by deny
}
3. Not paying attention to hook exit codes
# If the PreToolUse hook command always returns exit 0,
# scanning failures won't block execution
# ❌ Passes even on error
"command": "node scan.mjs"
# ✅ Explicitly control exit code
"command": "node scan.mjs || exit 1"
4. Accidentally gitignoring settings.json
Some teams accidentally add settings.json—which they want to share—to .gitignore. The correct approach is project settings under git, only settings.local.json in gitignore.
5. Forgetting to manually switch the production configuration
# ❌ Working on production with everyday settings
# ✅ Explicitly switch settings before production work
CLAUDE_SETTINGS=.claude/settings.production.json claude
Registering an alias makes it harder to forget:
# ~/.bashrc or ~/.zshrc
alias claude-prod='CLAUDE_SETTINGS=.claude/settings.production.json claude'
Debugging Your Configuration
When it’s unclear “why is this command being blocked”:
# Check current settings
claude --print-settings 2>/dev/null || cat .claude/settings.json
# Check which rule is matching (verbose mode)
claude --verbose -p "Run git push"
Summary: Best Practices for Permission Design
1. Start with deny
→ List commands you never want executed
→ rm -rf, git push --force, DROP TABLE are essential
2. Then configure ask
→ Write operations and deploy operations that need confirmation
3. Allow everything else
→ Read operations and CI operations: allow all for efficiency
4. Automate security with Hooks
→ Pre-commit scanning, auto type-check after edits
5. Prepare environment-specific config files
→ settings.json (development), settings.production.json (production)
With proper permission settings, you’ll stop mechanically clicking approve buttons and be able to focus only on operations that truly need review. Spend 30 minutes designing this upfront and hundreds of future work hours will be safer.
Related Articles
- Complete Security Guide for Claude Code
- 7 Claude Code Security Failure Cases
- CLAUDE.md Best Practices
References
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Just enter your email and we'll send you the single-page A4 cheatsheet right away.
We handle your data with care and never send spam.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
Claude Code Security Best Practices: API Keys, Permissions & Production Protection
A practical security guide for using Claude Code safely. From API key management to permission settings, Hooks-based automation, and production environment protection — with working code examples.
7 Claude Code Security Failure Cases | Real Incidents and Prevention
Seven real-world security incidents with Claude Code: .env leaks, production DB drops, billing explosions, and more — each with root cause analysis and prevention code.
The Complete Guide to Harness Engineering: Building AI Agents the Claude Code Way
Prompts alone can't tame an LLM. Learn how to weave tools, context, and control loops into a harness, with runnable code and Claude Code's own design as a teacher.