cheat sheet

Claude Code Settings and Permissions

Complete settings.json schema for Claude Code — permission rules, tool allowlists/denylists, environment variables, hooks, MCP, statusline, model selection, and file precedence.

Claude Code Settings and Permissions

What it is

Claude Code's settings system is a JSON-based configuration layer that controls which tools Claude may use, what shell commands require confirmation, MCP server registrations, environment variables, hooks, the statusline, and behavioral toggles. Settings live at two scopes — user-global (~/.claude/settings.json, applies everywhere) and project-local (.claude/settings.json, committed for team defaults) — with a git-ignored .claude/settings.local.json layered on top for personal overrides. Reach for settings.json whenever you find yourself accepting the same permission prompt twice, or when a teammate needs to inherit the same allow-list as you.

Settings file locations

Claude Code loads settings from multiple locations and merges them. More specific files override broader ones, but list-valued keys like permissions.allow are concatenated rather than replaced.

FileScopeWho edits it
~/.claude/settings.jsonUser-global (all projects)Personal defaults
<project>/.claude/settings.jsonProject (all users)Commit to repo — team defaults
<project>/.claude/settings.local.jsonProject (local only)Git-ignored — personal overrides

Precedence (highest to lowest): settings.local.json → project settings.json → user settings.json.

bash
# Show effective merged settings
claude /config

Output: (none — opens active settings in $EDITOR)

Full schema

The full set of top-level keys. Every key is optional; omit any you do not need.

json
{
  "model": "claude-opus-4-7",
  "permissions": {
    "allow": [],
    "deny": []
  },
  "env": {},
  "apiKeyHelper": "",
  "cleanupPeriodDays": 30,
  "systemPrompt": "",
  "appendSystemPrompt": "",
  "mcpServers": {},
  "hooks": {},
  "statusLine": {
    "type": "command",
    "command": ""
  },
  "outputStyle": "default",
  "includeCoAuthoredBy": true,
  "telemetry": true
}

model

Set the default model for all sessions opened with this settings file in scope. Override per-session with /model <id> or the --model CLI flag.

json
{
  "model": "claude-sonnet-4-6"
}

Valid values include "claude-opus-4-7", "claude-sonnet-4-6", and dated snapshots like "claude-haiku-4-5-20251001". Unknown IDs cause claude to error out on startup.

permissions

Control which tools Claude can use without prompting. Rules in allow are auto-approved; rules in deny are auto-rejected and override allow. When a tool call matches neither list, Claude prompts the user.

Permission rule syntax

text
"ToolName"                    // exact tool name — all uses
"ToolName(subcommand)"        // specific subcommand or pattern
"Bash(git *)"                 // glob: any git subcommand
"Bash(npm run *:npm test)"    // colon-delimited list of patterns
"mcp__<server>__<tool>"       // MCP-exposed tool

Allow rules

Pre-approve specific tools so Claude never prompts for them:

json
{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "LS",
      "Bash(git log:git diff:git status:git blame)",
      "Bash(npm test:npm run lint:npm run build)",
      "Bash(python -m pytest:ruff check:ruff format)"
    ]
  }
}

Deny rules

Always block specific tools regardless of what Claude requests. Deny takes precedence over allow, so you can broadly allow Bash while still blocking destructive subcommands.

json
{
  "permissions": {
    "deny": [
      "Bash(rm *:rmdir *)",
      "Bash(git push *:git force-push *)",
      "WebSearch",
      "WebFetch"
    ]
  }
}

Available tool names

ToolWhat it controls
BashShell command execution
ReadFile reading
EditFile editing (existing files)
WriteFile creation / overwrite
MultiEditMulti-file edits in one operation
GlobFile pattern matching
GrepContent search
LSDirectory listing
WebSearchWeb search queries
WebFetchFetching URLs
TodoReadReading task list
TodoWriteWriting task list
TaskSpawning subagents (see Subagents)
SkillInvoking a registered skill
mcp__<server>__<tool>Any MCP server tool

For CI/CD use --dangerously-skip-permissions to bypass all prompts. Only safe in isolated environments — never interactive use.

Read-only project example

Useful for code review or analysis tasks where you don't want Claude writing files:

json
{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "LS",
      "Bash(git log:git diff:git status:git show)"
    ],
    "deny": [
      "Edit",
      "Write",
      "MultiEdit",
      "Bash"
    ]
  }
}

Safe CI example

Allow tests and linting but block file writes and network:

json
{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(npm test:npm run lint:python -m pytest:ruff check)"
    ],
    "deny": [
      "Write",
      "Edit",
      "WebSearch",
      "WebFetch",
      "Bash(git push *)"
    ]
  }
}

env

Inject environment variables into every Claude Code session opened in this scope. Useful for pointing to dev tools, setting API keys for MCP servers, or configuring language runtimes.

json
{
  "env": {
    "NODE_ENV": "development",
    "DATABASE_URL": "postgresql://localhost/myapp_dev",
    "PYTHONPATH": "/home/alice/Code/myproject/src"
  }
}

Don't put ANTHROPIC_API_KEY in a committed settings file. Use settings.local.json, your shell profile, or apiKeyHelper instead.

apiKeyHelper

A shell command whose stdout is read as the API key. Useful when the key is in a secrets manager rather than an environment variable. The command runs once at startup.

json
{
  "apiKeyHelper": "op read op://dev/anthropic/api_key"
}
json
{
  "apiKeyHelper": "aws secretsmanager get-secret-value --secret-id anthropic --query SecretString --output text"
}

Output: (none — secret read at startup)

cleanupPeriodDays

How many days of session history to retain under ~/.claude/projects/<hash>/ before automatic cleanup. Default is 30. Bump it higher for projects you revisit infrequently; lower it to keep disk usage tight.

json
{
  "cleanupPeriodDays": 90
}

systemPrompt and appendSystemPrompt

systemPrompt replaces the default Claude Code system prompt entirely — only use this if you know what you're doing, as you lose the built-in tool descriptions and behavior guidance. appendSystemPrompt adds text to the existing prompt, which is almost always what you actually want.

json
{
  "appendSystemPrompt": "Always use Python 3.12+ syntax. Prefer match statements over if/elif chains where natural."
}

mcpServers

Register MCP servers persistently. The same servers can be added on the command line with claude mcp add, but settings.json is the canonical place for team-shared configuration. See MCP Servers for the full schema.

json
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres", "${DATABASE_URL}"]
    }
  }
}

${ENV_VAR} placeholders in mcpServers.*.args and mcpServers.*.env are expanded at startup from the shell environment, so secrets never need to land in the JSON file.

hooks

Run shell commands at lifecycle events: PreToolUse, PostToolUse, UserPromptSubmit, Notification, Stop, SubagentStop, and PreCompact. See Hooks for the full schema.

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {"type": "command", "command": "~/.claude/hooks/bash-guard.sh"}
        ]
      }
    ]
  }
}

statusLine

Configure the bottom statusline shown in the REPL. See Statusline for context variables, JSON input, and example scripts.

json
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh"
  }
}

outputStyle

Coarse control over Claude's response style. Default is "default"; alternatives include "concise" (terse, less narration), "explanatory" (more pedagogical), and "learning" (heavy narration suitable for pair-programming with a junior).

json
{
  "outputStyle": "concise"
}

includeCoAuthoredBy

Whether git commit messages created by Claude should include a Co-Authored-By: trailer. Defaults to true. Set to false for projects where commit attribution policy forbids it.

json
{
  "includeCoAuthoredBy": false
}

telemetry

Whether to send anonymous usage data to Anthropic. Defaults to true. Set to false to disable. Telemetry is also disabled when the environment variable CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 is set.

json
{
  "telemetry": false
}

Complete example: team project settings

A realistic .claude/settings.json for a Python web project — committed to the repo so every contributor inherits the same defaults.

json
{
  "model": "claude-sonnet-4-6",
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "LS",
      "Bash(git log:git diff:git status:git blame:git stash)",
      "Bash(npm install:npm test:npm run lint:npm run build)",
      "Bash(python -m pytest:ruff check:ruff format:mypy)"
    ],
    "deny": [
      "Bash(git push *:git push --force *)",
      "Bash(rm -rf *)",
      "WebFetch"
    ]
  },
  "env": {
    "NODE_ENV": "development",
    "PYTHONPATH": "src"
  },
  "appendSystemPrompt": "This is a FastAPI + SQLAlchemy 2.0 project. Use async def for routes. Tests live in tests/.",
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres", "${DATABASE_URL}"]
    }
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{"type": "command", "command": ".claude/hooks/bash-guard.sh"}]
      }
    ]
  },
  "cleanupPeriodDays": 60
}

Complete example: personal global settings

json
{
  "model": "claude-opus-4-7",
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "LS",
      "Bash(git log:git diff:git status)",
      "WebSearch"
    ]
  },
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh"
  },
  "outputStyle": "concise",
  "cleanupPeriodDays": 30
}

Environment variables (shell-level)

These variables affect Claude Code behavior when set in your shell before launching. They are the lowest-precedence layer — anything set in settings.json overrides them.

VariableEffect
ANTHROPIC_API_KEYAPI key (required unless apiKeyHelper is set)
ANTHROPIC_BASE_URLOverride API endpoint (proxy or custom deployment)
ANTHROPIC_MODELDefault model (overridden by settings.json model)
CLAUDE_CODE_MAX_OUTPUT_TOKENSCap output tokens per request
CLAUDE_CODE_USE_BEDROCK1 to use Amazon Bedrock
CLAUDE_CODE_USE_VERTEX1 to use Google Vertex AI
AWS_REGIONRequired when using Bedrock
ANTHROPIC_VERTEX_PROJECT_IDRequired when using Vertex
HTTP_PROXY / HTTPS_PROXYRoute traffic through a corporate proxy
NO_PROXYComma-separated hosts to bypass the proxy
NO_COLORDisable ANSI color output
DISABLE_AUTOUPDATERSet to 1 to disable automatic updates
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFICSet to 1 to block telemetry/update checks
BASH_DEFAULT_TIMEOUT_MSDefault Bash tool timeout in ms
BASH_MAX_TIMEOUT_MSMaximum allowed Bash tool timeout in ms
bash
export ANTHROPIC_API_KEY="sk-ant-..."
export ANTHROPIC_MODEL="claude-sonnet-4-6"
export DISABLE_AUTOUPDATER=1
claude

Output: (none — opens REPL with the specified configuration)

Permission rule grammar in depth

Permission rules use a simple grammar that combines a tool name with an optional parenthesised subcommand pattern. Patterns are not full shell globs — they match a prefix of the rendered command string.

text
"Bash(npm test)"           // matches: npm test
                           // matches: npm test --watch
                           // does not match: pnpm test

"Bash(npm test:npm run *)" // colon separates alternatives
                           // matches: npm test
                           // matches: npm run lint

"Bash(git *)"              // matches any git subcommand
                           // matches: git status
                           // matches: git push origin main  ← danger!

For destructive families (rm, git push, npm publish), prefer narrow allow rules over broad ones, and pair them with deny rules.

json
{
  "permissions": {
    "allow": ["Bash(git *)"],
    "deny":  ["Bash(git push *:git push --force *:git reset --hard *)"]
  }
}

Common pitfalls

  1. JSON commentssettings.json does not allow // comments; many editors highlight them as valid but claude rejects the file. Use a separate notes file instead.
  2. ~ not expanded in some contexts — paths in hooks.*.command and statusLine.command accept ~, but values inside mcpServers.*.args do not. Use absolute paths when in doubt.
  3. Deny does not override prompts for unlisted tools — if a tool is neither allowed nor denied, Claude still prompts. To silence prompts entirely, you must list everything.
  4. Settings.local.json committed by mistake — verify .claude/settings.local.json is in .gitignore; many templates omit it.
  5. Merge surprises with array keyspermissions.allow and permissions.deny arrays are concatenated, not replaced; a personal allow-list cannot remove an entry that the project file already added.
  6. Stale mcpServers env interpolation${VAR} is only expanded at session start; changing the shell variable after launch does not propagate.

Real-world recipes

Restrict Claude to a single subdirectory

Useful for sandbox projects where Claude should never touch files outside one folder.

json
{
  "permissions": {
    "allow": [
      "Read(src/*)",
      "Edit(src/*)",
      "Write(src/*)",
      "Glob",
      "Grep"
    ],
    "deny": [
      "Read(/etc/*:/home/alice/.ssh/*)",
      "Edit(*)",
      "Write(*)"
    ]
  }
}

Wire a secret manager

Pull the API key from 1Password at startup so it never lands on disk.

json
{
  "apiKeyHelper": "op read op://Personal/Anthropic/api_key"
}

Output: (none — secret resolved at session start)

Override the model only for a project

User-global says Opus, project-level says Haiku — Haiku wins because project settings have higher precedence.

json
{
  "model": "claude-haiku-4-5-20251001"
}

Project-aware system prompt

json
{
  "appendSystemPrompt": "This is a Cloudflare Workers project. Always prefer Workers APIs (KV, R2, D1) over Node-specific ones. The runtime is V8 isolates, not Node."
}

Quick diagnostic

bash
claude /doctor

Output:

text
✅ API key found
✅ Node.js 22.4.0
✅ @anthropic-ai/claude-code 1.x.x
✅ API connectivity: OK
✅ Settings: ~/.claude/settings.json, .claude/settings.json merged cleanly