settings.json
settings.json is the JSON configuration file Claude Code reads from four layered scopes — managed, user, project, and local — to decide model, permissions, hooks, environment variables, and everything else about how a session behaves.
What it is
Four files, in priority order. Managed settings are deployed by IT through server-managed settings, an MDM policy, or managed-settings.json in a system directory, and cannot be overridden. User settings at ~/.claude/settings.json apply across all your projects. Project settings at .claude/settings.json are committed and shared with the repository. Local settings at .claude/settings.local.json are yours alone. Command-line arguments sit between managed and local.
Most keys override: when the same key appears in two scopes, the higher-priority one wins. Permission rules are the exception — they merge, and a deny at any scope beats an allow at any other. Arrays such as claudeMdExcludes merge across layers too.
As of v2.1.211 Claude Code reads and writes .claude/settings.local.json at the root of the git repository, resolved through worktrees to the main checkout, so one file covers sessions started in any subdirectory or worktree. It stays in the starting directory in three cases: outside a git repository, when the repository root is your home directory, and in Agent SDK sessions. Claude Code adds the file to your global git excludes when it saves a setting there.
What it does for you
- It puts a team’s Claude Code setup in version control. Permission rules, hooks, and the model are reviewed in a pull request like any other configuration.
- It separates shared from personal cleanly. Project settings ship; local settings hold your own approvals and experiments without polluting the repository.
- It gives an organisation an enforcement layer. Managed settings cannot be overridden even by command-line flags, so a security policy holds on every machine.
How it works
01Claude Code loads every scope at startup
Managed, user, project, and local, plus anything passed with
--settings. Run/statusand read the Setting sources line to see which files loaded.02Values resolve by priority, rules by merge
Managed beats CLI arguments, which beat local, which beats project, which beats user. Permission rules merge across all of them, with deny evaluated before ask before allow.
03Most keys reload without a restart
Claude Code watches the files and applies changes to the running session, including
permissions,hooks, andapiKeyHelper. TheConfigChangehook fires for each detected change, with a matcher naming which source changed —user_settings,project_settings,local_settings,policy_settings, orskills— and a hook that exits 2 blocks the change from taking effect, except for policy settings.04Two keys wait for a restart
modelis read once at session start — use/modelto switch mid-session.outputStyleis part of the system prompt, which is rebuilt on/clearor restart.05Project allow rules wait for workspace trust
permissions.allowandpermissions.additionalDirectoriesin a project file grant capability, so Claude Code applies them only after you accept the workspace trust dialog. Deny and ask rules are unaffected.06Managed settings parse tolerantly, others do not
An invalid entry in managed settings is stripped with a warning and the rest of the policy is enforced, so one typo cannot disable an organisation’s whole policy. Security-enforcement fields fail closed instead: an invalid
allowedMcpServersis treated as an empty allowlist, and an invalidallowManagedMcpServersOnlyis treated astrue. A user, project, or local file that fails validation is rejected as a whole and reported to you at startup.
How to implement it
01Add the schema line first
Put
"$schema": "https://json.schemastore.org/claude-code-settings.json"at the top so your editor autocompletes keys and flags typos.02Decide what is shared and what is personal
Permission rules, hooks, and required environment variables go in
.claude/settings.jsonand get committed. Your model preference and your own approvals go in~/.claude/settings.jsonor.claude/settings.local.json.03Commit the project file and gitignore the local one
Claude Code adds
**/.claude/settings.local.jsonto your global git excludes when it writes there. If you create the file by hand, add it to.gitignoreyourself.04Verify the file actually loaded
Run
/statusand check Setting sources. A file with broken JSON does not appear at all, which is the fastest way to spot a syntax error.05Use /doctor when something is not taking effect
It reports unparseable settings files, duplicate installs, slow hooks, and unused skills or MCP servers against their context cost.
Examples
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)",
"Bash(git diff *)",
"Bash(git status *)"
],
"ask": ["Bash(git push *)"],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Edit(./db/migrate/**)"
],
"additionalDirectories": ["../shared-types"]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh",
"statusMessage": "Formatting",
"timeout": 60
}
]
}
]
},
"env": {
"NODE_ENV": "development",
"BASH_DEFAULT_TIMEOUT_MS": "300000"
},
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["registry.npmjs.org", "github.com", "*.github.com"]
}
},
"enabledMcpjsonServers": ["github", "postgres"],
"autoCompactWindow": 500000,
"cleanupPeriodDays": 90,
"enabledPlugins": ["mcp-server-dev@claude-plugins-official"]
}{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"model": "opus",
"permissions": {
"deny": [
"Read(//**/.env)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)"
]
},
"statusLine": {
"type": "command",
"command": "jq -r '.workspace.current_dir + \" · \" + .model.display_name'"
},
"cleanupPeriodDays": 60
}/status # the "Setting sources" line lists every file that loaded.
# A file with broken JSON does not appear at all.
/doctor # unparseable settings files, duplicate installs, slow hooks,
# unused skills and MCP servers against their context cost.
/config # the interactive settings UI
/config thinking=false # set one key without opening the UIUse it when
- Sharing permission rules and hooks with a team through a committed
.claude/settings.json. - Setting a personal default model and a blanket credential deny list that follows you into every project.
- Enforcing sandboxing and MCP server allowlists organisation-wide through managed settings.
- Turning on project-scoped MCP servers for everyone with
enabledMcpjsonServers. - Setting
autoCompactWindowso compaction fires at a size that suits how your team works.
Avoid it when
- You need a comment. JSON has none, so a settings file cannot explain itself — put the rationale in CLAUDE.md or the pull request instead.
- The value is a secret. Settings files are read as plain text and the project one is committed; use
envto reference a variable, or anapiKeyHelper. - You want a repository to grant itself capability. Project
allowrules wait for workspace trust, anddefaultMode: "auto"is ignored from project and local files by design. - The behaviour is conditional. Settings are static values; a hook can inspect the actual tool call and decide.
Common mistakes
SYMPTOMA settings file is edited and nothing changes.
FIXRun
/statusand read Setting sources. A file with a JSON syntax error does not appear at all — user, project, and local files are rejected as a whole when validation fails.SYMPTOMChanging
modelin settings mid-session has no effect.FIX
modelis read once at session start. Use/modelto switch mid-session; the settings value applies to new sessions.SYMPTOMA path rule in
~/.claude/settings.jsondoes not match the project directory it was written for.FIXA
/pathpattern anchors at the settings source, which for user settings is~/.claude. Use//for absolute paths or~/for home-relative ones.SYMPTOMAllow rules in a cloned repository’s
.claude/settings.jsondo nothing.FIXThey grant capability, so they apply only after you accept the workspace trust dialog for that folder. Deny and ask rules apply immediately.
SYMPTOM
.claude/settings.local.jsongets committed by accident.FIXClaude Code adds it to your global git excludes only when it writes a setting there itself. A file you create by hand needs a
.gitignoreentry from you.
Best practices
- Include the
$schemaline so your editor validates keys as you type them. - Commit
.claude/settings.jsonand keep.claude/settings.local.jsonout of git. - Put credential deny rules in user settings with
//or~/anchors so they apply in every project. - Run
/statusafter every edit; the Setting sources line is the fastest syntax check you have. - Keep secrets out of settings files — reference environment variables instead.
- Use managed settings for anything that must not be overridable, since no other scope, including CLI flags, can beat them.
Try it in five minutes
Watch settings precedence and validation behave.
- 1.Add
{"$schema":"https://json.schemastore.org/claude-code-settings.json","permissions":{"allow":["Bash(echo *)"]}}to.claude/settings.jsonin a test repository. - 2.Start
claude, run/status, and confirm the file appears under Setting sources. Run/permissionsand find the rule with its source. - 3.Add
{"permissions":{"deny":["Bash(echo *)"]}}to.claude/settings.local.jsonand ask Claude to runecho hi. It is refused: deny beats allow across scopes. - 4.Delete a closing brace from
.claude/settings.jsonand restart. Run/status— the file is gone from the list, because an invalid file is rejected whole. - 5.Fix the JSON, then run
/doctorand read what it reports about your configuration.
Related concepts
Verified against code.claude.com/docs/en/settings on 2026-08-09. See content/SOURCES.md for the full table.
← / → MOVE BETWEEN CONCEPTS