Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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:

MatcherTriggers OnExample Tools
`EditWrite`File modifications
EditFile edits onlyEdit
WriteFile writes onlyWrite
ReadFile readsRead
BashCommand executionBash
.*All toolsAny tool

Best Practices:

  1. Limit output: Use | head -N to prevent spam

    {
      "command": "npx tsc --noEmit 2>&1 | head -5 || true"
    }
  2. Always succeed: Use || true to avoid blocking

    {
      "command": "npm test -- --silent 2>&1 || true"
    }
  3. 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:

  1. 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
  2. Provide defaults: Use || for fallback values

    jq -r '.current_phase // "idle"' .agentful/state.json || echo "idle"
  3. Suppress errors: Use 2>/dev/null for cleaner output

    jq -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:

3

Lint Check

{
  "type": "command",
  "command": "npx eslint src/ --format json 2>&1 | jq '.length // 0' || echo '0'"
}

Output:

7

Progress 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 pending

Advanced 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 install
  • npm run build
  • npx tsc --noEmit
  • yarn add lodash
  • pnpm test

Version Control

{
  "allow": [
    "Bash(git:*)"
  ]
}

Allows:

  • git status
  • git add .
  • git commit -m "message"
  • git push

File Operations

{
  "allow": [
    "Bash(cat:*)",
    "Bash(echo:*)",
    "Bash(head:*)",
    "Bash(tail:*)",
    "Bash(grep:*)"
  ]
}

Allows:

  • cat package.json
  • echo "debug" >> log.txt
  • head -n 10 file.log

Build Tools

{
  "allow": [
    "Bash(node:*)",
    "Bash(ts-node:*)",
    "Bash(vite:*)",
    "Bash(next:*)"
  ]
}

Allows:

  • node script.js
  • ts-node src/index.ts
  • vite build
  • next dev

JSON Processing

{
  "allow": [
    "Bash(jq:*)"
  ]
}

Allows:

  • jq '.version' package.json
  • jq '.overall' .agentful/completion.json

Testing

{
  "allow": [
    "Bash(npm test:*)",
    "Bash(npm run test:*)",
    "Bash(vitest:*)",
    "Bash(jest:*)",
    "Bash(playwright:*)",
    "Bash(cypress:*)"
  ]
}

Allows:

  • npm test
  • npm run test:unit
  • vitest run
  • jest --coverage
  • playwright test

Database

{
  "allow": [
    "Bash(prisma:*)",
    "Bash(diesel:*)",
    "Bash(sequelize:*)"
  ]
}

Allows:

  • prisma migrate dev
  • prisma generate
  • diesel 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

  1. Check deny rules first - If match, deny
  2. Check allow rules - If match, allow
  3. 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.json

Security 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:

  1. Check JSON syntax:

    jq empty .claude/settings.json
  2. Verify hook type is valid:

    "PostToolUse"  // ✅ Valid
    // "PostTool"  // ❌ Invalid
  3. Ensure matcher pattern is correct:

    "matcher": "Edit|Write"  // ✅ Valid
    // "matcher": "Edit;Write"  // ❌ Invalid separator

Commands Blocked

Problem: "Permission denied" error.

Solutions:

  1. Check if command matches deny rule:

    # Review deny list
    jq '.permissions.deny' .claude/settings.json
  2. Verify allow pattern matches:

    # Test pattern matching
    echo "npm test" | grep -q "^npm:" && echo "Matches" || echo "No match"
  3. Add explicit allow rule:

    {
      "allow": [
        "Bash(your-command:*)"
      ]
    }

Hook Output Spam

Problem: Too much output from hooks.

Solutions:

  1. Limit output lines:

    {
      "command": "npm test 2>&1 | head -10 || true"
    }
  2. Use silent mode:

    {
      "command": "npm test -- --silent 2>&1 | tail -3 || true"
    }
  3. Filter with jq:

    {
      "command": "jq '.errors[0:3]' test-report.json 2>/dev/null || echo '[]'"
    }

Migration Notes

Version History

VersionChangesMigration
1.0.0Initial settings formatN/A

Future Changes

Potential additions:

  1. Rate limiting: Limit command frequency
  2. Resource limits: CPU/memory constraints
  3. Timeouts: Maximum command duration
  4. Logging: Audit trail of operations

Quick Reference

Hook Types

Hook TypeWhen It RunsCommon Use
PostToolUseAfter any tool operationType checking, linting
UserPromptSubmitAfter user inputDisplay state, progress

Permission Categories

CategoryExample CommandsRisk Level
Package managersnpm install, npx tscLow
Build toolsvite build, next buildLow
Testingnpm test, vitestLow
Version controlgit status, git pushMedium
File operationscat, echo, jqLow
Systemrm, dd, mkfsHigh (always deny)

Common Patterns

PatternDescriptionExample
Bash(npm:*)All npm commandsnpm test, npm install
Bash(npx:*)All npx commandsnpx tsc, npx prettier
Bash(git:*)All git commandsgit status, git commit
Edit|WriteFile modificationsAny edit or write
.*\.tsx?$TypeScript filesMatch .ts, .tsx

See Also