nikolas.sapa
April 16, 2026

02 — Settings & Permissions

settings.json is where Claude Code gets its boundaries. What tools it can run freely, what needs a confirmation, and what's off-limits. Done right, you get autonomous work on routine tasks and a prompt before anything destructive.


1. The Three-Layer Config

Claude Code reads settings from three places, in order of increasing specificity:

LayerPathScope
Global~/.claude/settings.jsonApplies everywhere
Project.claude/settings.jsonThis repo only
Local.claude/settings.local.jsonYour machine, not committed

Each layer merges on top of the previous. Use global for behavior you want everywhere, project for team-shared rules, and local for machine-specific overrides (API keys, paths, tooling that isn't on every dev's machine).


2. Permission Modes

Every tool call falls into one of three modes:

  • allow — runs without prompting
  • ask — Claude pauses and waits for confirmation
  • deny — blocked entirely

You configure these as arrays in settings.json:

{
  "permissions": {
    "allow": [
      "Bash(git log*)",
      "Bash(npm run build*)",
      "mcp__myserver__some_tool"
    ],
    "ask": [
      "Bash(rm *)",
      "Bash(git push --force*)"
    ],
    "deny": []
  }
}

The pattern syntax uses glob-style wildcards. Bash(git log*) allows any git log invocation. Bash(rm *) stops Claude before any file deletion and asks you first.

A practical split: auto-approve read operations and common build commands; require confirmation for anything destructive (deletion, force pushes, database drops).


3. The defaultMode Flag

{
  "defaultMode": "bypassPermissions"
}

bypassPermissions removes the generic per-call prompt. Claude still respects your allow/ask/deny lists — this flag removes the runtime nag for things not explicitly listed.

Pair it with skipDangerousModePermissionPrompt: true to suppress the startup warning. Use this only once you trust your ask list covers the dangerous cases.


4. The env Block

Environment variables you want available in every Claude session:

{
  "env": {
    "CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1",
    "CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet"
  }
}

Two flags worth knowing:

  • CLAUDE_CODE_DISABLE_AUTO_MEMORY — prevents Claude from auto-summarizing sessions into its built-in memory. Useful if you manage context manually.
  • CLAUDE_CODE_SUBAGENT_MODEL — sets which model spawned subagents use by default. Keeps costs predictable.

Set whichever variables your tooling or workflow needs. This is also where you expose things like NODE_ENV or a custom API base URL without having to set them in every terminal session.


5. Enabling MCP Tools

MCP tool calls follow the same allow/ask/deny system. The tool name format is mcp__<server>__<tool>:

{
  "permissions": {
    "allow": [
      "mcp__notion__notion-search",
      "mcp__playwright__browser_navigate"
    ]
  }
}

If you're using enableAllProjectMcpServers: true in your local settings, all project-defined MCP servers activate without per-server approval. Useful on your own machine; don't commit it to shared project settings.


6. Hooks

Hooks let you run shell commands at lifecycle events. Three useful moments:

{
  "hooks": {
    "Stop": [{ "type": "command", "command": "echo 'Claude finished'" }],
    "Notification": [{ "type": "command", "command": "echo 'Claude needs input'" }],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "my-rewrite-script" }]
      }
    ]
  }
}

Stop fires on session end. Notification fires when Claude pauses for input. PreToolUse with a matcher lets you intercept specific tool calls before they execute — useful for logging, rewriting commands, or enforcing patterns at the harness level rather than in the prompt.

Adapt these to your OS and workflow. macOS users often wire them to osascript for system notifications.