Settings Reference
Complete reference for .claude/settings.json configuration including hooks, permissions, and all available options.
Overview
The settings.json file controls Claude Code's behavior during agentful development sessions. It configures:
- Hooks - Automatic triggers for commands and validation
- Permissions - Allowed and denied operations
- Security - Safety limits on file operations
Location
.claude/settings.json
Schema
{
"type": "object",
"required": ["hooks", "permissions"],
"properties": {
"hooks": {
"type": "object",
"description": "Event-driven command triggers"
},
"permissions": {
"type": "object",
"description": "Allow/deny rules for operations"
}
}
}Complete Example
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -5 || true"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "if [ -f .agentful/state.json ]; then jq -r '.current_phase // \"idle\"' .agentful/state.json 2>/dev/null || echo 'idle'; else echo 'idle'; fi"
}
]
}
]
},
"permissions": {
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(node:*)",
"Bash(git:*)",
"Bash(cat:*)",
"Bash(echo:*)",
"Bash(jq:*)"
],
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~/.*)",
"Bash(rm -rf /.*)",
"Bash(dd:*)",
"Bash(mkfs:*)"
]
}
}Hooks
Hooks are event-driven commands that run automatically during development.
Hook Types
PostToolUse
Runs after any tool operation (Read, Write, Edit, Bash, etc.).
Use Cases:
- Type checking after code changes
- Linting after edits
- Test running after implementations
- Validation after file operations
Schema:
{
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -5 || true"
}
]
}
]
}Matcher Values:
| Matcher | Triggers On | Example Tools |
|---|---|---|
| `Edit | Write` | File modifications |
Edit | File edits only | Edit |
Write | File writes only | Write |
Read | File reads | Read |
Bash | Command execution | Bash |
.* | All tools | Any tool |
Best Practices:
-
Limit output: Use
| head -Nto prevent spam{ "command": "npx tsc --noEmit 2>&1 | head -5 || true" } -
Always succeed: Use
|| trueto avoid blocking{ "command": "npm test -- --silent 2>&1 || true" } -
Quick checks: Favor fast validation
{ "command": "npx tsc --noEmit --incremental false 2>&1 | head -3 || true" }
UserPromptSubmit
Runs when user submits a new prompt.
Use Cases:
- Display current state context
- Show pending decisions
- Indicate active phase
- Provide progress updates
Schema:
{
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "if [ -f .agentful/state.json ]; then jq -r '.current_phase // \"idle\"' .agentful/state.json 2>/dev/null || echo 'idle'; else echo 'idle'; fi"
}
]
}
]
}Best Practices:
-
Handle missing files: Check file existence first
if [ -f .agentful/state.json ]; then jq -r '.current_phase // "idle"' .agentful/state.json else echo "idle" fi -
Provide defaults: Use
||for fallback valuesjq -r '.current_phase // "idle"' .agentful/state.json || echo "idle" -
Suppress errors: Use
2>/dev/nullfor cleaner outputjq -r '.current_phase' .agentful/state.json 2>/dev/null || echo "idle"
Hook Command Examples
TypeScript Check
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -5 || true"
}Output:
src/auth.ts:45:12 - error TS2322: Type 'string' is not assignable to type 'number'Test Runner
{
"type": "command",
"command": "npm test -- --silent --reporter=json 2>&1 | jq -r '.stats.failures' || echo '0'"
}Output:
3Lint Check
{
"type": "command",
"command": "npx eslint src/ --format json 2>&1 | jq '.length // 0' || echo '0'"
}Output:
7Progress Display
{
"type": "command",
"command": "jq -r '\"Progress: \\(.overall)%\"' .agentful/completion.json 2>/dev/null || echo 'Progress: 0%'"
}Output:
Progress: 48%Pending Decisions
{
"type": "command",
"command": "jq -r 'if .pending | length > 0 then \"\\(.pending | length) decisions pending\" else empty end' .agentful/decisions.json 2>/dev/null || ''"
}Output:
2 decisions pendingAdvanced Hook Patterns
Conditional Hooks
Run different commands based on conditions:
{
"type": "command",
"command": "if git diff --name-only | grep -q '\\.tsx?#x27;; then npx tsc --noEmit 2>&1 | head -3 || true; fi"
}Chain Multiple Commands
{
"type": "command",
"command": "npx tsc --noEmit && npm test -- --silent || true"
}File-Specific Hooks
Only run for certain file types:
{
"matcher": ".*\\.(ts|tsx)quot;,
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -3 || true"
}
]
}Permissions
Permissions control which operations Claude Code can perform.
Permission Syntax
<ToolName>(<Pattern>)Allow Rules
Operations that are explicitly permitted.
Package Managers
{
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(yarn:*)",
"Bash(pnpm:*)",
"Bash(bun:*)"
]
}Allows:
npm installnpm run buildnpx tsc --noEmityarn add lodashpnpm test
Version Control
{
"allow": [
"Bash(git:*)"
]
}Allows:
git statusgit add .git commit -m "message"git push
File Operations
{
"allow": [
"Bash(cat:*)",
"Bash(echo:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(grep:*)"
]
}Allows:
cat package.jsonecho "debug" >> log.txthead -n 10 file.log
Build Tools
{
"allow": [
"Bash(node:*)",
"Bash(ts-node:*)",
"Bash(vite:*)",
"Bash(next:*)"
]
}Allows:
node script.jsts-node src/index.tsvite buildnext dev
JSON Processing
{
"allow": [
"Bash(jq:*)"
]
}Allows:
jq '.version' package.jsonjq '.overall' .agentful/completion.json
Testing
{
"allow": [
"Bash(npm test:*)",
"Bash(npm run test:*)",
"Bash(vitest:*)",
"Bash(jest:*)",
"Bash(playwright:*)",
"Bash(cypress:*)"
]
}Allows:
npm testnpm run test:unitvitest runjest --coverageplaywright test
Database
{
"allow": [
"Bash(prisma:*)",
"Bash(diesel:*)",
"Bash(sequelize:*)"
]
}Allows:
prisma migrate devprisma generatediesel migration run
Deny Rules
Operations that are explicitly forbidden, regardless of allow rules.
System Safety
{
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~/.*)",
"Bash(rm -rf /.*)",
"Bash(dd:*)",
"Bash(mkfs:*)",
"Bash(fdisk:*)",
"Bash(format:*)"
]
}Blocks:
- Deleting root directory
- Deleting home directory
- Disk formatting
- Partition table manipulation
Data Destruction
{
"deny": [
"Bash(git clean -fdx)",
"Bash(git reset --hard)",
"Bash(git push --force)",
"Bash(rm -rf node_modules)",
"Bash(rm -rf .git)"
]
}Blocks:
- Removing untracked files
- Hard resetting git history
- Force pushing to remote
- Deleting node_modules
- Deleting git history
Sensitive Commands
{
"deny": [
"Bash(curl -X POST *",
"Bash(wget *",
"Bash(ssh *)",
"Bash(scp *)",
"Bash(rsync *)"
]
}Blocks:
- Arbitrary POST requests
- File downloads
- Remote shell access
- Remote file copy
Permission Evaluation Order
- Check deny rules first - If match, deny
- Check allow rules - If match, allow
- Default deny - If no match, deny
Example:
{
"permissions": {
"allow": [
"Bash(git:*)"
],
"deny": [
"Bash(git push --force)"
]
}
}Result:
git status✅ Allowed (matches allow)git push✅ Allowed (matches allow)git push --force❌ Denied (matches deny, deny takes precedence)rm -rf node_modules❌ Denied (no allow match)
Pattern Matching
Wildcard (*)
Matches any characters:
{
"allow": [
"Bash(npm test:*)" // npm test, npm test:unit, npm test:integration
]
}Exact Match
No wildcards for exact command:
{
"allow": [
"Bash(npm test)" // Only "npm test", not "npm test:unit"
]
}Prefix Match
Pattern at start matches prefix:
{
"allow": [
"Bash(npm:*)" // npm install, npm test, npm run build
]
}Multiple Patterns
Use array for multiple patterns:
{
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(yarn:*)"
]
}Complete Settings Example
Development Mode
Permissive settings for active development:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -5 || true"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "jq -r '\"Phase: \" + (.current_phase // \"idle\")' .agentful/state.json 2>/dev/null || echo 'Phase: idle'"
}
]
}
]
},
"permissions": {
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(node:*)",
"Bash(git:*)",
"Bash(cat:*)",
"Bash(echo:*)",
"Bash(jq:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(grep:*)",
"Bash(vitest:*)",
"Bash(playwright:*)"
],
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~/.*)",
"Bash(rm -rf /.*)",
"Bash(dd:*)",
"Bash(mkfs:*)",
"Bash(git push --force)",
"Bash(git clean -fdx)"
]
}
}Production Mode
Strict settings for production builds:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit && npm test -- --silent 2>&1 || true"
}
]
}
]
},
"permissions": {
"allow": [
"Bash(npm test:*)",
"Bash(npm run build:*)",
"Bash(git status)",
"Bash(git diff)",
"Bash(cat:*)",
"Bash(jq:*)"
],
"deny": [
"Bash(npm install *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git push *)",
"Bash(rm -rf *)"
]
}
}Autonomous Mode
Settings for 24/7 autonomous development:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -3 || true"
},
{
"type": "command",
"command": "npm test -- --silent --reporter=json 2>&1 | jq -r '.stats.failures // 0' || echo '0'"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.overall' .agentful/completion.json 2>/dev/null || echo '0'"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "if [ -f .agentful/decisions.json ]; then COUNT=$(jq '.pending | length' .agentful/decisions.json 2>/dev/null || echo '0'); if [ \"$COUNT\" -gt 0 ]; then echo \"⚠️ $COUNT pending decisions - run /agentful-decide\"; fi; fi"
}
]
}
]
},
"permissions": {
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(node:*)",
"Bash(git:*)",
"Bash(cat:*)",
"Bash(echo:*)",
"Bash(jq:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(grep:*)",
"Bash(mkdir:*)",
"Bash(cp:*)",
"Bash(mv:*)"
],
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~/.*)",
"Bash(rm -rf /.*)",
"Bash(dd:*)",
"Bash(mkfs:*)",
"Bash(git push --force)",
"Bash(git clean -fdx)",
"Bash(rm -rf node_modules)",
"Bash(rm -rf .git)"
]
}
}Hook and Permission Patterns
Common Hook Combinations
TypeScript + Tests
{
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 | head -3 || true"
},
{
"type": "command",
"command": "npm test -- --silent 2>&1 | tail -3 || true"
}
]
}
]
}Lint + Format Check
{
"PostToolUse": [
{
"matcher": "Edit|Write.*\\.(ts|tsx|js|jsx)quot;,
"hooks": [
{
"type": "command",
"command": "npx eslint --fix 2>&1 | head -5 || true"
}
]
}
]
}Progress Tracking
{
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "jq -r '\"Progress: \\(.overall)% | Phase: \\(.current_phase)\"' .agentful/state.json 2>/dev/null || echo 'Progress: 0% | Phase: idle'"
}
]
}
]
}Permission Patterns by Use Case
Frontend Development
{
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(vite:*)",
"Bash(next:*)",
"Bash(tailwindcss:*)"
]
}Backend Development
{
"allow": [
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(prisma:*)",
"Bash(node:*)",
"Bash(ts-node:*)"
]
}Testing
{
"allow": [
"Bash(npm test:*)",
"Bash(npm run test:*)",
"Bash(vitest:*)",
"Bash(jest:*)",
"Bash(playwright:*)",
"Bash(cypress:*)"
]
}Database Operations
{
"allow": [
"Bash(prisma migrate:*)",
"Bash(prisma generate:*)",
"Bash(prisma db:*)",
"Bash(prisma studio:*)"
]
}Validation and Testing
Testing Hooks
Test hook commands manually:
# Test TypeScript hook
npx tsc --noEmit 2>&1 | head -5 || true
# Test state reading
jq -r '.current_phase // "idle"' .agentful/state.json 2>/dev/null || echo "idle"
# Test progress display
jq -r '"Progress: \(.overall)%"' .agentful/completion.json 2>/dev/null || echo "Progress: 0%"Testing Permissions
Verify permission rules:
# Check if command is allowed (should work)
npm test
# Check if command is denied (should be blocked)
rm -rf /Validating JSON
# Validate JSON syntax
jq empty .claude/settings.json
# Pretty print for inspection
jq '.' .claude/settings.jsonSecurity Best Practices
1. Principle of Least Privilege
Only allow what's necessary:
{
"allow": [
"Bash(npm test)" // ✅ Specific command
]
// NOT: "Bash(npm:*)" // ❌ Too broad
}2. Explicit Deny for Dangerous Operations
Always deny destructive commands:
{
"deny": [
"Bash(rm -rf *)",
"Bash(dd *)",
"Bash(mkfs *)"
]
}3. Limit Hook Output
Prevent terminal spam:
{
"command": "npx tsc --noEmit 2>&1 | head -5 || true" // ✅ Limited
// NOT: "npx tsc --noEmit" // ❌ Unlimited output
}4. Use Non-Blocking Hooks
Always append || true:
{
"command": "npm test -- --silent || true" // ✅ Continues on failure
// NOT: "npm test -- --silent" // ❌ Blocks on failure
}5. Sanitize External Input
When processing user input:
{
"command": "jq -r '.current_phase' .agentful/state.json 2>/dev/null || echo 'idle'" // ✅ Safe
// NOT: direct string interpolation // ❌ Injection risk
}Troubleshooting
Hooks Not Running
Problem: Hook commands don't execute.
Solutions:
-
Check JSON syntax:
jq empty .claude/settings.json -
Verify hook type is valid:
"PostToolUse" // ✅ Valid // "PostTool" // ❌ Invalid -
Ensure matcher pattern is correct:
"matcher": "Edit|Write" // ✅ Valid // "matcher": "Edit;Write" // ❌ Invalid separator
Commands Blocked
Problem: "Permission denied" error.
Solutions:
-
Check if command matches deny rule:
# Review deny list jq '.permissions.deny' .claude/settings.json -
Verify allow pattern matches:
# Test pattern matching echo "npm test" | grep -q "^npm:" && echo "Matches" || echo "No match" -
Add explicit allow rule:
{ "allow": [ "Bash(your-command:*)" ] }
Hook Output Spam
Problem: Too much output from hooks.
Solutions:
-
Limit output lines:
{ "command": "npm test 2>&1 | head -10 || true" } -
Use silent mode:
{ "command": "npm test -- --silent 2>&1 | tail -3 || true" } -
Filter with jq:
{ "command": "jq '.errors[0:3]' test-report.json 2>/dev/null || echo '[]'" }
Migration Notes
Version History
| Version | Changes | Migration |
|---|---|---|
| 1.0.0 | Initial settings format | N/A |
Future Changes
Potential additions:
- Rate limiting: Limit command frequency
- Resource limits: CPU/memory constraints
- Timeouts: Maximum command duration
- Logging: Audit trail of operations
Quick Reference
Hook Types
| Hook Type | When It Runs | Common Use |
|---|---|---|
PostToolUse | After any tool operation | Type checking, linting |
UserPromptSubmit | After user input | Display state, progress |
Permission Categories
| Category | Example Commands | Risk Level |
|---|---|---|
| Package managers | npm install, npx tsc | Low |
| Build tools | vite build, next build | Low |
| Testing | npm test, vitest | Low |
| Version control | git status, git push | Medium |
| File operations | cat, echo, jq | Low |
| System | rm, dd, mkfs | High (always deny) |
Common Patterns
| Pattern | Description | Example |
|---|---|---|
Bash(npm:*) | All npm commands | npm test, npm install |
Bash(npx:*) | All npx commands | npx tsc, npx prettier |
Bash(git:*) | All git commands | git status, git commit |
Edit|Write | File modifications | Any edit or write |
.*\.tsx?$ | TypeScript files | Match .ts, .tsx |
See Also
- CLI Reference - Command-line interface
- State Files Reference - State file formats
- Commands - Available slash commands
- Configuration - Setup and customization