One schema, several files
Claude Code behavior (permissions, hooks, default model, environment variables and more) is configured in settings.json files at different scopes:
| Scope | File | Shared? | Use it for |
|---|---|---|---|
| User | ~/.claude/settings.json | No | Your personal defaults across all projects |
| Project | .claude/settings.json | Yes, commit it | Team rules: permissions, hooks, shared config |
| Local | .claude/settings.local.json | No, gitignored | Your personal tweaks for this one project |
| Managed | System location set by IT/MDM or server-delivered | Org-wide | Policies that can't be overridden |
Precedence: who wins?
When the same setting appears in more than one place, the more specific and more authoritative scope wins:
- Managed settings (highest, can't be overridden)
- Command-line flags for this session
- Local project settings
- Project settings
- User settings (lowest)
Some keys merge instead of replacing. Permission rules from different scopes are combined, and hooks from every scope all run. A deny rule anywhere still denies.
- ⋮⋮Managed settings
- ⋮⋮Command-line flags
- ⋮⋮.claude/settings.local.json
- ⋮⋮.claude/settings.json
- ⋮⋮~/.claude/settings.json
A realistic project settings file
{
"permissions": {
"allow": ["Bash(pnpm test *)", "Bash(pnpm lint *)", "Bash(git diff *)"],
"ask": ["Bash(git push *)"],
"deny": ["Read(./.env)", "Read(./secrets/**)"]
},
"env": {
"NODE_ENV": "development"
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "npx prettier --write --ignore-unknown \"$(jq -r '.tool_input.file_path')\"" }]
}
]
}
}This one file pre-approves safe test and lint commands, makes pushes ask first, blocks reading secrets and auto-formats every edit, for everyone on the team.
Handy keys to know
| Key | What it controls |
|---|---|
permissions | allow / ask / deny rules, defaultMode, extra directories |
hooks | Commands that run on lifecycle events (Unit 7) |
env | Environment variables for every session |
model | Default model for sessions |
statusLine | A custom status line command |
outputStyle | The default output style |
Inspect before you guess
/configopens the settings UI./statusshows your current setup: account, model, and which settings sources loaded./permissionslists every active rule and where it came from.
.claude/settings.json is committed, so never put API keys there. Use .claude/settings.local.json (gitignored), your shell environment, or a secrets manager.
Check your understanding
Which settings file should a team commit to share permission rules and hooks?
Which scope has the highest precedence?
If one scope allows Read(./.env) and another denies it, what happens?
Where should a personal API key for one project go?