Generic Guide: Configuring Claude Code Settings
Based on my research, here's comprehensive advice for configuring Claude Code for any project:
Configuration File Strategy
Choose the Right Location
- Project-Level (.claude/settings.json) - RECOMMENDED for teams
- Shared with entire team via git
- Enforces consistent behavior across all developers
- Best for: CI/CD workflows, team standards, security policies
- Commit this file to version control
- User-Level (~/.claude/settings.json) - For personal defaults
- Applies globally to all your projects
- Good for: Personal preferences, frequently-used tools
- Never share credentials here
- Use sparingly to avoid conflicts with project settings
- Local-Level (.claude/settings.local.json) - For personal overrides
- Git-ignored, personal only
- Overrides project settings for your machine
- Best for: Personal API keys, machine-specific paths, experimental settings
- Add to .gitignore
Precedence Order
Enterprise Policy > User Settings > Project Settings > Local Settings (deny always wins)
Permission Configuration Pattern
The Three-Tier Approach
Use a traffic light mental model:
{ "permissions": { "allow": [], // π’ Green zone - safe, autonomous "ask": [], // π‘ Yellow zone - needs confirmation "deny": [] // π΄ Red zone - blocked completely } }
- Green Zone (Allow) - Safe Operations
Philosophy: Read-only operations, reversible changes, standard dev workflow
Always safe to allow: "allow": [ // Git read operations "Bash(git status)", "Bash(git diff*)", "Bash(git log*)", "Bash(git show*)", "Bash(git branch*)",
// Build & test (non-destructive)
"Bash(npm run test*)",
"Bash(npm run build*)",
"Bash(npm run lint*)",
"Bash(make test)",
"Bash(make build)",
// File inspection
"Bash(cat *)",
"Bash(ls *)",
"Bash(pwd)",
"Bash(head *)",
"Bash(tail *)",
"Bash(find *)",
"Bash(grep *)",
"Bash(which *)",
// File operations (project files only)
"Read(./src/**)",
"Read(./tests/**)",
"Read(./docs/**)",
"Write(./src/**)",
"Write(./tests/**)",
"Edit",
// Web operations
"WebSearch"
]
- Red Zone (Deny) - Dangerous Operations
Philosophy: Destructive, irreversible, or security-sensitive
Always deny: "deny": [ // Destructive commands "Bash(rm -rf *)", "Bash(rm -r *)", "Bash(sudo *)", "Bash(chmod 777 *)",
// Credential files
"Read(./.env*)",
"Read(./**/.env*)",
"Read(./secrets/**)",
"Read(**/.aws/**)",
"Read(**/.ssh/**)",
"Read(*.pem)",
"Read(*.key)",
"Read(*.crt)",
"Read(id_rsa*)",
// Lock files (prevent corruption)
"Write(package-lock.json)",
"Write(pnpm-lock.yaml)",
"Write(yarn.lock)",
"Write(Cargo.lock)",
"Write(Gemfile.lock)",
"Write(composer.lock)",
"Write(poetry.lock)",
"Write(uv.lock)",
// Publishing/deployment
"Bash(npm publish*)",
"Bash(docker push*)",
"Bash(kubectl delete*)",
// Network operations (unless needed)
"Bash(curl *)",
"Bash(wget *)"
]
- Yellow Zone (Ask) - Requires Judgment
Philosophy: Impactful but sometimes necessary
Typically ask for: "ask": [ // Package management "Bash(npm install*)", "Bash(npm uninstall*)", "Bash(pnpm add*)", "Bash(pip install*)",
// Git write operations
"Bash(git push*)",
"Bash(git merge*)",
"Bash(git rebase*)",
"Bash(git reset --hard*)",
// PR/deployment operations
"Bash(gh pr create*)",
"Bash(gh pr merge*)",
"Bash(make deploy*)",
// External data
"WebFetch"
]
Bash Permission Syntax
Critical Rules
// β Correct patterns "Bash(git status)" // Exact match only "Bash(git diff*)" // Matches: git diff, git diff HEAD, git diff --cached "Bash(npm run *)" // Matches: npm run test, npm run build, etc. "Bash(make *)" // Matches all make commands
// β Common mistakes "Bash(git.*)" // Doesn't work - not regex "Bash(git (status|diff))" // Doesn't work - no alternation "Bash(test)" // Too broad - matches "delete test"
Pattern Examples
// Specific command "Bash(git status)"
// Command family with space "Bash(git diff *)" // git diff anything
// Command prefix (no space) "Bash(git diff*)" // git diff, git difftool, git diff-tree
// Tool with all subcommands "Bash(docker compose *)"
// Glob-style (works for simple cases) "Bash(npm run test*)" // test, test:unit, test:integration
Workflow-Specific Configurations
Configuration 1: Autonomous Development
Use case: Full-featured development with minimal interruption
{ "permissions": { "allow": [ "Bash(git status)", "Bash(git diff*)", "Bash(git log*)", "Bash(git add*)", "Bash(git commit*)", "Bash(npm run *)", "Bash(make *)", "Bash(cat *)", "Bash(ls *)", "Bash(grep )", "Read(./src/)", "Read(./tests/)", "Write(./src/)", "Write(./tests/)", "Edit", "WebSearch" ], "deny": [ "Bash(rm -rf )", "Bash(sudo )", "Read(./.env)", "Write(lock)" ], "ask": [ "Bash(git push)", "Bash(npm install)" ], "defaultMode": "acceptEdits" } }
Configuration 2: Safe Exploration/Research
Use case: Understanding codebases, read-only analysis
{ "permissions": { "allow": [ "Bash(git status)", "Bash(git diff*)", "Bash(git log*)", "Bash(cat *)", "Bash(ls )", "Bash(grep )", "Bash(find )", "Read", "WebSearch" ], "deny": [ "Write", "Edit", "Bash(git commit)", "Bash(git push)", "Read(./.env)" ], "defaultMode": "plan" } }
Configuration 3: CI/CD Pipeline
Use case: Automated testing and deployment
{ "permissions": { "allow": [ "Bash(npm ci)", "Bash(npm run test*)", "Bash(npm run build)", "Bash(npm run lint)", "Bash(git status)", "Bash(git diff*)", "Read", "Write(./dist/)", "Write(./coverage/)" ], "deny": [ "Bash(npm install)", // Use npm ci instead "Bash(git push*)", "Write(./.env*)", "Write(lock)" ], "defaultMode": "acceptEdits" } }
Configuration 4: PR Review Bot
Use case: Automated code review
{ "permissions": { "allow": [ "Bash(gh pr view*)", "Bash(gh pr diff*)", "Bash(git diff*)", "Bash(npm run test*)", "Bash(npm run lint)", "Read", "WebSearch" ], "deny": [ "Write", "Edit", "Bash(git push*)", "Bash(gh pr merge*)" ], "ask": [ "Bash(gh pr comment*)" ] } }
Tool-Specific Patterns
Node.js Projects
"allow": [ "Bash(npm run *)", "Bash(pnpm run )", "Bash(yarn run )", "Bash(npx )" ], "deny": [ "Write(package-lock.json)", "Write(pnpm-lock.yaml)", "Write(yarn.lock)" ], "ask": [ "Bash(npm install)", "Bash(pnpm add)", "Bash(yarn add)" ]
Python Projects
"allow": [ "Bash(python -m pytest*)", "Bash(python -m unittest*)", "Bash(python -m mypy*)", "Bash(python -m black*)", "Bash(uv run )", "Bash(poetry run )" ], "deny": [ "Write(poetry.lock)", "Write(Pipfile.lock)", "Write(uv.lock)" ], "ask": [ "Bash(pip install)", "Bash(poetry add)", "Bash(uv add*)" ]
Docker Projects
"allow": [ "Bash(docker ps*)", "Bash(docker logs*)", "Bash(docker inspect*)", "Bash(docker compose up*)", "Bash(docker compose down*)" ], "deny": [ "Bash(docker rm -f*)", "Bash(docker system prune*)", "Bash(docker push*)" ], "ask": [ "Bash(docker build*)" ]
Git Operations
"allow": [ // Safe read operations "Bash(git status)", "Bash(git diff*)", "Bash(git log*)", "Bash(git show*)", "Bash(git branch*)",
// Local write operations
"Bash(git add*)",
"Bash(git commit*)",
"Bash(git checkout*)",
"Bash(git stash*)",
"Bash(git pull*)"
], "ask": [ // Remote operations "Bash(git push*)",
// History rewriting
"Bash(git merge*)",
"Bash(git rebase*)",
"Bash(git reset --hard*)",
"Bash(git cherry-pick*)"
]
File Operation Patterns
Granular File Access
{ "permissions": { "allow": [ // Source code "Read(./src/)", "Write(./src/)",
// Tests
"Read(./tests/**)",
"Write(./tests/**)",
// Documentation
"Read(./docs/**)",
"Write(./docs/**)",
"Write(*.md)",
// Config (read-only)
"Read(*.json)",
"Read(*.yaml)",
"Read(*.toml)"
],
"deny": [
// Credentials
"Read(./.env*)",
"Read(./config/secrets*)",
// Build artifacts
"Write(./dist/**)",
"Write(./build/**)",
// Dependencies
"Write(./node_modules/**)",
"Write(./.venv/**)"
]
}
}
Broad Access (Less Secure)
{ "permissions": { "allow": [ "Read", // Read EVERYTHING "Write", // Write ANYWHERE "Edit" // Edit ANYTHING ], "deny": [ // Still protect credentials "Read(./.env*)", "Read(/.aws/)", "Write(lock)" ] } }
Security Best Practices
- Defense in Depth
Don't rely solely on Claude Code permissions:
- Run Claude Code in containers or VMs
- Use OS-level permissions (read-only mounts)
- Employ git hooks for validation
- Use dedicated service accounts with limited privileges
docker run -v $(pwd)/src:/workspace/src:ro
-v $(pwd)/.claude:/workspace/.claude:ro
claude-code
- Principle of Least Privilege
Start restrictive, expand as needed:
// Week 1: Minimal "allow": ["Bash(git status)", "Read(./src/**)", "WebSearch"]
// Week 2: Add testing "allow": ["Bash(git status)", "Bash(npm test)", "Read(./src/**)", "WebSearch"]
// Week 3: Add commits "allow": ["Bash(git status)", "Bash(git commit*)", "Bash(npm test)", "Read(./src/)", "Write(./src/)", "WebSearch"]
- Credential Protection Strategy
Multi-layer approach:
{ "permissions": { "deny": [ // File patterns "Read(./.env*)", "Read(.//.env*)", "Read(./secrets/)", "Read(./config/credentials*)",
// File extensions
"Read(*.pem)",
"Read(*.key)",
"Read(*.crt)",
"Read(*.p12)",
"Read(*.pfx)",
// Directory patterns
"Read(**/.aws/**)",
"Read(**/.ssh/**)",
"Read(**/.gnupg/**)",
"Read(*/secrets/**)",
// Specific files
"Read(./.npmrc)",
"Read(./.pypirc)",
"Read(./credentials.json)"
]
}
}
Plus use .gitignore: .env* secrets/ *.pem .key credentials
Plus environment variables (never in files): export API_KEY="secret" # Good echo "API_KEY=secret" > .env # Bad
- Lock File Protection
Why it matters: Lock files ensure reproducible builds
"deny": [ // Node.js "Write(package-lock.json)", "Write(pnpm-lock.yaml)", "Write(yarn.lock)",
// Python
"Write(poetry.lock)",
"Write(Pipfile.lock)",
"Write(uv.lock)",
// Rust
"Write(Cargo.lock)",
// Ruby
"Write(Gemfile.lock)",
// PHP
"Write(composer.lock)",
// Go
"Write(go.sum)"
]
Advanced Configuration Options
Default Modes
{ "permissions": { "defaultMode": "acceptEdits" // or "plan" or "default" } }
Modes:
- "default" - Prompts first time for each tool
- "acceptEdits" - Auto-approves file modifications
- "plan" - Read-only, planning mode only
- "bypassPermissions" - DANGEROUS - skips all checks
Environment Variables
{ "env": { "NODE_ENV": "development", "DEBUG": "true", "CI": "false" } }
Hooks (Advanced)
{
"hooks": {
"pre-commit": "npm run lint && npm test",
"post-edit": "prettier --write
Testing Your Configuration
- Validation Checklist
claude-code "Show me git status" claude-code "Run the tests" claude-code "Read src/main.js"
claude-code "Install a new package" claude-code "Push to remote"
claude-code "Delete everything with rm -rf" claude-code "Show me the .env file"
- JSON Validation
jq . .claude/settings.json
jsonschema -i .claude/settings.json
https://json.schemastore.org/claude-code-settings.json
- Permission Audit
Create a test script:
#!/bin/bash
echo "Testing allowed commands..." claude-code "Run git status" || echo "FAIL: git status" claude-code "Run make test" || echo "FAIL: make test"
echo "Testing denied commands..." claude-code "Read .env" && echo "FAIL: .env readable" || echo "PASS: .env denied"
echo "Testing file operations..." claude-code "Read src/main.js" || echo "FAIL: can't read source" claude-code "Write to src/test.txt" || echo "FAIL: can't write source"
Common Pitfalls & Solutions
Pitfall 1: Too Permissive Initially
β Bad: "allow": ["Bash(*)", "Read", "Write"]
β Good: "allow": [ "Bash(git status)", "Bash(npm run test)", "Read(./src/)", "Write(./src/)" ]
Pitfall 2: Forgetting Lock Files
β Bad: "allow": ["Write"] // Will corrupt package-lock.json
β Good: "allow": ["Write(./src/)", "Write(./tests/)", "Write(*.md)"], "deny": ["Write(lock)"]
Pitfall 3: Credentials in Project Settings
β Bad: // .claude/settings.json (committed to git) { "env": { "API_KEY": "sk-secret123" } }
β Good: // .claude/settings.local.json (git-ignored) { "env": { "API_KEY": "sk-secret123" } }
Pitfall 4: Overly Broad Patterns
β Bad: "allow": ["Bash(rm)"] // Matches "rm -rf" too!
β Good: "allow": [ "Bash(npm run *)", "Bash(yarn run *)" ], "deny": ["Bash(rm *)"]
Pitfall 5: Not Testing Patterns
β Assumption: "Bash(git diff*)" // Does this match "git diff HEAD"?
β Test it:
claude-code "Run git diff HEAD"
Migration Strategy
From No Config β Full Config
Phase 1: Observation (Week 1) { "permissions": { "ask": ["*"] // Everything requires approval } } Monitor what commands Claude Code needs.
Phase 2: Safe Operations (Week 2) { "permissions": { "allow": [ "Bash(git status)", "Bash(git diff*)", "Read(./src/**)" ], "ask": ["*"] } }
Phase 3: Development Workflow (Week 3) { "permissions": { "allow": [ "Bash(git status)", "Bash(git diff*)", "Bash(git add*)", "Bash(git commit*)", "Bash(npm run test)", "Read(./src/)", "Write(./src/)" ], "deny": [ "Bash(rm -rf )", "Read(./.env)" ] } }
Phase 4: Full Automation (Week 4+) { "permissions": { "allow": [/* comprehensive list /], "deny": [/ security boundaries /], "ask": [/ impactful operations */], "defaultMode": "acceptEdits" } }
Project Type Templates
Minimal Web App
{ "permissions": { "allow": [ "Bash(npm run )", "Bash(git status)", "Bash(git diff)", "Bash(git add*)", "Bash(git commit*)", "Read(./src/)", "Write(./src/)", "WebSearch" ], "deny": [ "Read(./.env*)", "Write(package-lock.json)" ], "ask": ["Bash(git push*)"] } }
Monorepo (Multi-language)
{ "permissions": { "allow": [ "Bash(pnpm run )", "Bash(make )", "Bash(uv run )", "Bash(turbo )", "Bash(git status)", "Bash(git diff)", "Bash(git add)", "Bash(git commit)", "Read(./apps/)", "Read(./packages/)", "Write(./apps/)", "Write(./packages/)", "WebSearch" ], "deny": [ "Read(./.env)", "Write(lock)", "Bash(rm -rf )" ], "ask": [ "Bash(pnpm install)", "Bash(git push*)" ], "defaultMode": "acceptEdits" } }
Data Science / Jupyter
{ "permissions": { "allow": [ "Bash(jupyter *)", "Bash(python -m )", "Bash(pip list)", "Bash(conda list)", "Read(./notebooks/)", "Read(./data/)", "Write(./notebooks/)", "Write(./output/)", "WebSearch" ], "deny": [ "Write(./data/**)", // Prevent data corruption "Bash(rm -rf )" ], "ask": [ "Bash(pip install)", "Bash(conda install)" ] } }
Troubleshooting
Issue: Still Getting Prompts
Check:
- Exact pattern match: "Bash(npm run test)" vs "Bash(npm run test*)"
- Spacing: "Bash(git diff )" vs "Bash(git diff)"
- Tool name capitalization: "Bash" not "bash"
- Conflicting user settings
- Enterprise policies overriding
Debug:
claude-code --show-config
claude-code --test-permission "Bash(npm run test)"
Issue: Permissions Too Broad
Symptom: Claude Code can do things it shouldn't
Fix: Add explicit denies "allow": ["Bash(npm )"], // Too broad "deny": ["Bash(npm publish)"] // Block specific case
Issue: Can't Access Needed Files
Symptom: "Permission denied" errors
Fix: Check file patterns // Wrong "Read(./src/*)" // Only top-level files
// Right "Read(./src/**)" // Recursive
Final Recommendations
- Start Simple
{ "permissions": { "allow": [ "Bash(git status)", "Read(./src/**)", "WebSearch" ] } }
- Add Security Denies
{ "permissions": { "allow": ["..."], "deny": [ "Bash(rm -rf )", "Read(./.env)", "Write(lock)" ] } }
- Enable Workflow
{ "permissions": { "allow": ["...", "Bash(npm run *)", "Write(./src/**)"], "deny": ["..."] } }
- Add Confirmations
{ "permissions": { "allow": ["..."], "deny": ["..."], "ask": [ "Bash(npm install*)", "Bash(git push*)" ] } }
- Set Default Mode
{ "permissions": { "allow": ["..."], "deny": ["..."], "ask": ["..."], "defaultMode": "acceptEdits" } }
- Document & Share
{ "$schema": "https://json.schemastore.org/claude-code-settings.json", "_comment": "Team config for autonomous development", "permissions": { // ... your config } }
Key Takeaways
- Permissions use prefix matching, not regex
- Start restrictive, expand gradually
- Always deny credentials and destructive operations
- Use "ask" for impactful but necessary operations
- Commit project settings, ignore local settings
- Test your configuration before deploying
- Don't rely on permissions alone for security
- Document why specific permissions are needed
This configuration approach enables autonomous Claude Code operation while maintaining security boundaries appropriate for your project's risk tolerance.
I liked your notes because they focus on what Claude is allowed to do, not just how to prompt it.
One thing I am trying to sanity-check: after permissions are set, do you still want a receipt that proves what checks actually ran? Like changed files, risky areas, commands, and any final "tests passed" claim that has no command behind it.
Would that help in a team repo, or is it overkill?