Claude Code settings.json: the complete reference

Four settings files and your command line can apply at once, and they are merged in a specific order with specific rules per key. Most confusing configuration problems are really precedence or merge problems, so start there.

By the Continuum team. We build a workbench that runs Claude Code, Codex, and their peers, so the model rates quoted here are the ones our own cost analytics ship with.

The short version

Settings load from four files plus command-line arguments. Precedence runs managed policy, then command-line arguments, then .claude/settings.local.json, then .claude/settings.json, then ~/.claude/settings.json. Managed policy cannot be overridden by anything, including CLI flags. Most arrays concatenate across layers rather than replacing, so a deny rule anywhere applies everywhere. The main groups are permissions, env, hooks, model, sandbox, and statusLine.

What you need to know
  • Four files plus CLI args, merged. Managed policy always wins, even over the command line.
  • Commit .claude/settings.json; gitignore settings.local.json.
  • Arrays concatenate across layers. A deny rule at any level blocks the tool everywhere.
  • permissions, hooks, and env reload live. model and outputStyle need a restart.
  • Project allow rules apply only after you accept the workspace trust dialog.

The files and their order

Highest precedence first. Verified against Anthropic docs, August 2026.

LayerWhereCommit?
Managed policySystem path, MDM plist, Windows registry, or server-managed settingsDeployed by IT
Command line--settings, --allowedTools, --permission-mode, and friendsn/a
Local.claude/settings.local.jsonNo, gitignore
Project.claude/settings.jsonYes
User~/.claude/settings.jsonNo

How the layers merge

This is the part that surprises people. Settings are not a simple "highest layer wins" overlay: most arrays concatenate and de-duplicate across every layer.

Merge behaviour by key.

KeysBehaviour
permissions.allow, permissions.deny, permissions.ask, env, claudeMdExcludes, allowedHttpHookUrls, allowedMcpServers, deniedMcpServersConcatenate and de-duplicate across layers
fallbackModelDoes not merge; the highest-precedence file supplies the whole chain
Scalar keys such as model, outputStyle, cleanupPeriodDaysHighest-precedence layer wins

When a change takes effect

Reloads live on file changeRead once at startup
permissions, hooks, env, apiKeyHelper, claudeMdExcludes, autoMemoryDirectorymodel, outputStyle

A complete configuration

.claude/settings.json - committed, for the team.
{
  "model": "claude-sonnet-5",

  "permissions": {
    "allow": [
      "Bash(npm run test *)",
      "Bash(npm run lint)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Read(src/**)",
      "Edit(src/**)"
    ],
    "ask": [
      "Bash(git push *)"
    ],
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(**/credentials/**)",
      "Bash(rm -rf *)"
    ],
    "additionalDirectories": ["../shared-types"],
    "defaultMode": "acceptEdits"
  },

  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["registry.npmjs.org", "*.github.com"]
    }
  },

  "env": {
    "NODE_ENV": "development",
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://otel.internal.example.com"
  },

  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh" }
        ]
      }
    ]
  },

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

  "cleanupPeriodDays": 30
}

The groups you will use most.

KeyPurpose
modelDefault model for new sessions. Read once at startup
permissions.allow / ask / denyRule arrays, evaluated deny then ask then allow
permissions.defaultModeStarting permission mode for the session
permissions.additionalDirectoriesFile access outside the project root. Does not load config from there
sandboxOS-level filesystem and network isolation for the Bash tool
envVariables set for every session and every subprocess
hooksDeterministic commands on lifecycle events
statusLineCommand rendering the prompt status line
cleanupPeriodDaysSession-file retention. Default 30, minimum 1
autoMemoryEnabledWhether Claude writes its own memory notes. Default true
claudeMdExcludesGlob patterns for CLAUDE.md files to skip, useful in monorepos
availableModelsRestrict which models the session, subagents, and skills can select
disableAllHooksKill switch for hooks and the custom status line
includeCoAuthoredByWhether commits carry a co-author trailer
apiKeyHelperShell command that mints an auth token

Permission patterns

The syntax is Tool(specifier), and getting the specifiers right is most of the value.

Pattern shapes that behave the way you expect.

PatternMatches
Bash(npm run test *)Any command starting npm run test. The space before * forces a word boundary
Bash(npm run test:*)Same thing; the :* suffix is equivalent to a trailing *
Bash(ls*)ls -la and lsof, because there is no boundary
Read(src/**)Anything under src/, relative to the current directory
Edit(/src/**)Relative to the settings file that declares it, not the cwd
Read(//**/.env)Any .env anywhere on the filesystem
Read(~/.ssh/**)Path from your home directory
WebFetch(domain:*.example.com)Any subdomain, but not example.com itself
mcp__linearEvery tool on the linear MCP server
Agent(Explore)The Explore subagent

Two wrapper behaviours are worth knowing because they bite in opposite directions. Claude Code strips a fixed set of wrappers before matching (timeout, time, nice, nohup, stdbuf, command, builtin, bare xargs), so Bash(npm test *) also matches timeout 30 npm test. It does not strip environment runners such as npx, docker exec, mise exec, or devbox run, so Bash(devbox run *) would allow devbox run rm -rf ..

Debugging settings

# is the JSON even valid? this is the answer surprisingly often
jq . .claude/settings.json

# test a config without touching your real one
claude --settings /tmp/experiment.json
claude --settings '{"model":"opus"}'

# run with only some layers, useful for reproducing a CI result
claude --setting-sources user,project

# inside a session
/permissions        # every rule and the file it came from
/hooks              # every configured hook, grouped by event
/doctor             # setup checkup that also proposes fixes
/config key=value   # change one option without opening the settings UI

Common causes, in the order worth checking.

SymptomCause
Setting ignored entirelyA managed policy overrides it, or the key is managed-only
Nothing in the file appliesInvalid JSON, usually a trailing comma
Works for you, not a colleagueIt is in settings.local.json
Denied despite an allow ruleA deny or ask rule matches first; order beats specificity
Allow rules ignored on a fresh cloneWorkspace trust dialog not accepted yet
Env var not visible to a toolSet in the shell after launch, not in env
Model change did not takemodel is read once at startup; use /model mid-session

Questions people ask

.claude/settings.json in a project, ~/.claude/settings.json for all your work, .claude/settings.local.json for personal project overrides, plus a managed policy file an organisation can deploy through MDM, the Windows registry, or server-managed settings.

Managed policy is highest and cannot be overridden, not even by command-line flags. Below it: command-line arguments, then .claude/settings.local.json, then .claude/settings.json, then ~/.claude/settings.json.

Both, depending on the key. Scalars such as model take the highest-precedence value, while most arrays including permissions.allow, permissions.deny, env, and claudeMdExcludes concatenate and de-duplicate across every layer.

Yes for .claude/settings.json, so permissions and hooks are reviewed like code. No for .claude/settings.local.json, which is for personal overrides and should be gitignored.

Usually a deny or ask rule matching the same call, since rules are evaluated deny, ask, allow and the first match wins. Otherwise a managed policy, invalid JSON preventing the whole file loading, or an unaccepted workspace trust dialog suppressing project allow rules.

Use the env object in settings.json. Those variables reach every session and every subprocess Claude Code spawns. Setting them in your shell after launching Claude Code has no effect on the running session.

Pass --settings with a path to a temporary file or an inline JSON string. It applies for that session only and leaves your real configuration alone. Use --setting-sources to reproduce a run with only some layers loaded.

model and outputStyle are read once at startup. permissions, hooks, env, apiKeyHelper, claudeMdExcludes, and autoMemoryDirectory reload when the file changes, so an edit mid-session takes effect without relaunching.

Sources

Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.

  1. Claude Code settings reference
  2. Claude Code permissions reference
  3. Claude Code hooks reference
Try it

Your config,
your agents.

Continuum drives the Claude Code you configured, so settings, hooks, and permissions apply exactly as they do in the terminal.

free app · your subscriptions · local-first