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.
- Four files plus CLI args, merged. Managed policy always wins, even over the command line.
- Commit
.claude/settings.json; gitignoresettings.local.json. - Arrays concatenate across layers. A deny rule at any level blocks the tool everywhere.
permissions,hooks, andenvreload live.modelandoutputStyleneed a restart.- Project
allowrules apply only after you accept the workspace trust dialog.
The files and their order
Highest precedence first. Verified against Anthropic docs, August 2026.
| Layer | Where | Commit? |
|---|---|---|
| Managed policy | System path, MDM plist, Windows registry, or server-managed settings | Deployed by IT |
| Command line | --settings, --allowedTools, --permission-mode, and friends | n/a |
| Local | .claude/settings.local.json | No, gitignore |
| Project | .claude/settings.json | Yes |
| User | ~/.claude/settings.json | No |
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.
| Keys | Behaviour |
|---|---|
permissions.allow, permissions.deny, permissions.ask, env, claudeMdExcludes, allowedHttpHookUrls, allowedMcpServers, deniedMcpServers | Concatenate and de-duplicate across layers |
fallbackModel | Does not merge; the highest-precedence file supplies the whole chain |
Scalar keys such as model, outputStyle, cleanupPeriodDays | Highest-precedence layer wins |
When a change takes effect
| Reloads live on file change | Read once at startup |
|---|---|
permissions, hooks, env, apiKeyHelper, claudeMdExcludes, autoMemoryDirectory | model, outputStyle |
A complete configuration
{
"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.
| Key | Purpose |
|---|---|
model | Default model for new sessions. Read once at startup |
permissions.allow / ask / deny | Rule arrays, evaluated deny then ask then allow |
permissions.defaultMode | Starting permission mode for the session |
permissions.additionalDirectories | File access outside the project root. Does not load config from there |
sandbox | OS-level filesystem and network isolation for the Bash tool |
env | Variables set for every session and every subprocess |
hooks | Deterministic commands on lifecycle events |
statusLine | Command rendering the prompt status line |
cleanupPeriodDays | Session-file retention. Default 30, minimum 1 |
autoMemoryEnabled | Whether Claude writes its own memory notes. Default true |
claudeMdExcludes | Glob patterns for CLAUDE.md files to skip, useful in monorepos |
availableModels | Restrict which models the session, subagents, and skills can select |
disableAllHooks | Kill switch for hooks and the custom status line |
includeCoAuthoredBy | Whether commits carry a co-author trailer |
apiKeyHelper | Shell 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.
| Pattern | Matches |
|---|---|
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__linear | Every 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.
| Symptom | Cause |
|---|---|
| Setting ignored entirely | A managed policy overrides it, or the key is managed-only |
| Nothing in the file applies | Invalid JSON, usually a trailing comma |
| Works for you, not a colleague | It is in settings.local.json |
| Denied despite an allow rule | A deny or ask rule matches first; order beats specificity |
| Allow rules ignored on a fresh clone | Workspace trust dialog not accepted yet |
| Env var not visible to a tool | Set in the shell after launch, not in env |
| Model change did not take | model 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.