Settings and Configuration
settings.json layers (user, project, local), env vars, model selection, and the configuration precedence rules.
What it is#
Claude Code reads its behaviour from a small set of settings.json files plus a handful of environment variables. The settings files come in three layers — user, project, and project-local — that are merged into one effective configuration at session start. This is where you set the default model, the permission mode, the allowlist of safe Bash commands, the hooks, the MCP servers, the env vars Claude should see, and a handful of UI knobs.
Memory and CLAUDE.md are about what Claude knows; settings are about how Claude behaves. The two systems are complementary and live in different files for that reason. A CLAUDE.md line tells the model “this project uses pnpm”; a settings.json entry tells the tool “auto-approve pnpm test.”
When to use it#
Settings is the right place for anything that is true of how the tool runs in a given context, not anything you’d want the model to read as prose. Specifically:
- Permission policy. Default mode, allow lists, deny lists, hook definitions. Anything in the safety boundary belongs here, not in
CLAUDE.md. - Model and runtime selection. Default model for the layer, max tokens, temperature overrides, whether to enable extended thinking, whether to allow background tasks.
- Hooks and MCP servers. Both are configured by JSON in
settings.jsonand both are layer-aware (project-local hooks override project hooks). - Env vars Claude should see. A scoped way to expose secrets and config to Claude without leaking them into prose.
- UI and notification preferences. Status-line scripts, notification routing, autoupdate behaviour.
What does not belong in settings: project conventions (those go in CLAUDE.md), user preferences about response tone (memory), or one-off rules for a single session (just say them in the prompt).
How it works#
The three layers#
Settings are read from three files, in this precedence order (later overrides earlier):
- User —
~/.claude/settings.json. Loaded for every Claude Code session you run, regardless of project. The right place for things that are true of you across every codebase. - Project —
<repo>/.claude/settings.json. Loaded for any session under the repo. Committed to git so the team shares it. The right place for project conventions like “auto-approve our test commands.” - Project-local —
<repo>/.claude/settings.local.json. Loaded for sessions under the repo. Gitignored by convention. The right place for personal overrides on top of the team’s project settings, or for env vars / secrets the team file can’t carry.
Effective config is a deep merge: scalars override, objects merge key-by-key, arrays concatenate-then-dedupe (with explicit suffixes available to control direction). When unsure, the TUI’s /settings command shows the effective merged config.
Environment variables come in at session start and can override specific fields via CLAUDE_* names. These take precedence over all three files for the variables they set.
What’s in a settings.json#
A representative settings file:
{ "model": "claude-sonnet-4-5", "permissions": { "defaultMode": "acceptEdits", "allow": [ "Bash(pnpm test*)", "Bash(pnpm lint*)", "Bash(git status)", "Bash(git diff*)" ], "deny": [ "Bash(rm -rf /*)", "Bash(git push --force*)" ] }, "hooks": { "PreToolUse": [ { "matcher": "Bash", "command": ".claude/hooks/bash-audit.sh" } ] }, "env": { "BIGQUERY_PROJECT": "my-team-analytics" }, "statusLine": { "type": "command", "command": ".claude/statusline.sh" }}Each top-level key is its own subsystem. The merge between layers happens key by key, so a project file can set model and hooks while a user file sets statusLine and env, and the merged result has all three.
Precedence in detail#
When the same field is set in multiple places:
- Project-local beats project beats user. Local overrides repo overrides global.
permissions.allowandpermissions.denyconcatenate across layers, then deduplicate. A user-level allowlist plus a project-level allowlist gives you both, not just the highest-precedence one. This is intentional — adding an entry doesn’t accidentally drop existing entries.permissions.denyalways wins overpermissions.allow. If something is denied at any layer, it stays denied at every layer. The safety direction is one-way.- Hooks compose. Hooks from all three layers all fire for matching events. Order is user → project → project-local.
- Env vars layered:
envkeys override left-to-right. Project-local can override project, which can override user. Process env vars (set in your shell before launching Claude) override all of them.
The merge logic is permissive on the “add more capability” axis and strict on the “deny” axis. This means a project file can grant a permission a user file didn’t, but a user file’s deny is honoured even when the project file would allow it. Useful for personal safety preferences.
Environment variables#
A small set of CLAUDE_* env vars override settings at runtime:
| Variable | Effect |
|---|---|
CLAUDE_MODEL | Overrides model |
CLAUDE_PERMISSION_MODE | Overrides permissions.defaultMode |
CLAUDE_CONFIG_DIR | Changes the user-config directory away from ~/.claude/ |
ANTHROPIC_API_KEY | Authenticates via API key instead of subscription sign-in |
CLAUDE_DISABLE_AUTOUPDATE | Set to 1 to pin the current version |
These are useful for CI (“always run on Sonnet, in bypass mode, with this API key”), for ephemeral overrides (“just this once, use Opus”), and for tools that wrap Claude Code and want to inject config.
Configuration#
The full hierarchy at a glance#
~/.claude/settings.json (user — every project, every session)<repo>/.claude/settings.json (project — committed, shared with team)<repo>/.claude/settings.local.json (project-local — gitignored, personal)<environment variables> (highest precedence, runtime only)In addition, ~/.claude/CLAUDE.md and <repo>/CLAUDE.md are the prose-instructions analogues at user and project scope. They’re not in settings.json because they’re for the model to read, not for the tool to parse.
Common top-level keys#
The keys most engineers configure in their first month:
model— default model for sessions in this scope.permissions.defaultMode—default/acceptEdits/plan/bypass.permissions.allow/permissions.deny— Bash and tool patterns to auto-approve or block. See the permission rules page.hooks— event handlers keyed by event type (PreToolUse,PostToolUse,UserPromptSubmit,Stop).mcpServers— MCP server definitions. Same shape as the MCP CLI accepts.env— environment variables to expose to Claude (and child tool processes).statusLine— custom status-line definition.notifications— notification routing (terminal beep, system notification, custom command).
A small number of less-common keys cover advanced cases: model.max_tokens for output cap, autoupdate to pin behaviour, theme for the TUI palette, editorMode for the input editor’s keybindings.
Examples#
Minimal user settings.json#
{ "model": "claude-sonnet-4-5", "permissions": { "defaultMode": "acceptEdits" }}Sonnet by default; accept-edits mode by default. Two lines, applied to every project.
A project settings.json for a Node monorepo#
{ "permissions": { "allow": [ "Bash(pnpm test*)", "Bash(pnpm lint*)", "Bash(pnpm typecheck*)", "Bash(pnpm build*)", "Bash(git status)", "Bash(git diff*)", "Bash(git log*)" ] }, "hooks": { "PostToolUse": [ { "matcher": "Edit", "command": ".claude/hooks/format-on-edit.sh" } ] }}Allowlist covers the routine read and test commands; a PostToolUse hook runs the formatter after every edit. The whole file is six logical settings and saves ten confirmations per session.
A project-local settings.local.json with a personal override#
{ "model": "claude-opus-4-1", "env": { "MY_DEBUG_FLAG": "true", "BIGQUERY_PROJECT": "personal-sandbox" }}Personal preference: I run this repo on Opus. Personal env vars for a debugging flag and a BigQuery project that points at my sandbox rather than the team’s. Lives in settings.local.json so it doesn’t leak into team config.
Gotchas#
- Settings are read at session start; restart to pick up changes. Edit
settings.jsonmid-session and Claude doesn’t see it until the nextclaudelaunch. The TUI’s/reload-settingscommand (where available) re-reads without a full restart, but not all settings re-apply cleanly mid-session — model and permission changes are safe; hook changes mostly are; MCP server changes typically aren’t. settings.local.jsonis gitignored by default but not enforced. The install’s.gitignoreexcludes it; if you don’t have that line, the file commits. Check before pushing.permissions.allowpatterns use a glob-ish syntax that is not shell glob.Bash(pnpm test*)matchespnpm test,pnpm test:watch, etc.Bash(pnpm test *)(with a space) matches differently. The exact semantics are documented in the permission-rules page; assume nothing without testing.denyis one-way. If the user file deniesBash(rm -rf*)and the project file allowsBash(rm -rf node_modules), the deny wins. There is no way to “unset” a deny from a higher-precedence layer. Design the user file’s deny list to cover only patterns that are always unsafe.- Env vars in
envare exposed to Bash child processes. Asecretinsettings.json’senvends up inprocess.envof any Bash tool call that spawns a process. This is usually what you want for things likeBIGQUERY_PROJECT; never what you want for raw secrets you’d prefer not to leak into a process tree. - The merged config is sometimes surprising. Use
/settingsto dump the effective config when in doubt. Trying to predict it from three files is error-prone; reading what the tool actually computed is reliable. - Auto-update can change behaviour overnight. A new Claude Code release may add a setting whose default differs from what you’d choose. Skim release notes; if you want strict reproducibility, pin the version.
Related features#
- CLAUDE.md and Memory Files
- Permission Modes
- Installation and First Run
- Plan Mode
- The Conversation Loop
The split most teams arrive at after a month
User-global settings.json for personal defaults (model, mode, theme). Project settings.json for shared safety: the allowlist of routine team commands and the hooks that enforce house style. Project-local settings.local.json only for env vars, personal model overrides, and anything you’d be unhappy seeing show up in a teammate’s PR. CLAUDE.md for prose conventions Claude reads. Memory for things only the model needs to remember about you. Each system stays in its lane and the friction drops to near zero.