Claude Code settings.json — the complete reference
settings.json is Claude Code's configuration file. It controls what Claude
is allowed to do without asking (permissions), the environment
it runs in (env), which model it uses, and what happens at lifecycle
events (hooks). It's plain JSON — no build step, no CLI wizard
required — which is why a generator like the one above is all you need.
Three files, three scopes
The same schema lives at three paths, and which one you edit decides who it applies to:
| File | Scope | Use it for |
|---|---|---|
~/.claude/settings.json |
User — every project on this machine | Personal defaults: model choice, telemetry opt-outs, rules you want everywhere |
.claude/settings.json |
Project — checked into git, shared with the team | Team conventions: the test commands everyone allows, the secrets everyone denies |
.claude/settings.local.json |
Local — this repo, just you | Personal experiments and machine-specific paths; Claude Code gitignores it for you |
Precedence: who wins when settings conflict
More specific beats more general. From highest priority to lowest:
- Enterprise managed policies — deployed by an IT admin
(
managed-settings.jsonin the system directory); users can't override these - Command-line arguments — per-session flags like
--model - Local project settings —
.claude/settings.local.json - Shared project settings —
.claude/settings.json - User settings —
~/.claude/settings.json
For permissions specifically, rules from every file are merged — and a
deny rule wins over an allow rule regardless of which file it comes from.
You can allow Bash broadly in your user settings and a project's deny list
will still block Bash(curl:*).
The permissions block
This is the block most people come here for. Five keys matter:
"permissions": {
"allow": [ "Bash(npm run test:*)" ],
"ask": [ "Bash(git push:*)" ],
"deny": [ "Read(./.env)", "WebFetch" ],
"additionalDirectories": [ "../docs/" ],
"defaultMode": "acceptEdits"
}
- allow — matching tool calls run without a prompt.
- ask — matching calls always prompt for confirmation, even when a broader allow rule matches.
- deny — matching calls are blocked outright. Deny beats allow and ask.
This is also the mechanism for keeping files away from Claude entirely: a
Readdeny rule stops the file being read at all. - additionalDirectories — extra directories (beyond the working directory) Claude may access.
- defaultMode — the permission mode a session starts in:
default(prompt on first use of each tool),acceptEdits(auto-accept file edits),plan(read-only), orbypassPermissions(no prompts at all — sandboxed containers only). Two more exist for narrower cases:dontAsk(auto-deny anything not pre-approved) andauto(auto-approve with background safety checks — a research preview).
Permission rule syntax
Every rule is either a bare tool name (WebFetch matches every use of the
tool) or a tool name with a specifier: Tool(specifier). The specifier's
meaning depends on the tool:
| Tool | Rule example | What it matches |
|---|---|---|
Bash |
Bash(npm run build) |
Exactly that command, nothing else |
Bash(npm run test:*) |
Any command starting with npm run test — the :*
suffix is the prefix wildcard | |
Read / Edit |
Edit(docs/**) |
Files matching the pattern — gitignore-style globs (see path forms below) |
WebFetch |
WebFetch(domain:example.com) |
Fetches from that domain only |
| MCP tools | mcp__github |
Every tool exposed by the github MCP server |
mcp__github__get_issue |
One specific tool on that server. Wildcards work after the
mcp__server__ prefix — mcp__github__* matches every tool
on the server and mcp__github__get_* matches its get_
tools — but the server segment itself can't be a wildcard |
Bash rule gotchas
Bash(git diff:*)allowsgit diff HEAD~1but a plainBash(git diff)allows only the two-word command, exactly.- Prefix matching is text matching, not shell parsing. Claude Code is aware of
chaining —
safe-cmd && scary-cmddoesn't sneak through an allow rule forsafe-cmd— but options still count as prefix text:Bash(npm run test:*)also matchesnpm run test -- --dangerous-flag. - Prefer allowing narrow, specific scripts (
npm run lint) over broad runners (npm:*).
Read/Edit path forms
Read and Edit rules follow the gitignore spec, with four ways to anchor a path:
| Pattern | Anchored to | Example |
|---|---|---|
//path | Filesystem root (absolute) | Read(//etc/hosts) |
~/path | Your home directory | Read(~/.zshrc) |
/path | The directory containing the settings file | Edit(/src/**) |
path or ./path | The current working directory | Read(./.env) |
The single leading slash is the one that surprises people: Edit(/src/**)
is not an absolute path — it's relative to wherever the settings file lives.
Use // when you really mean the filesystem root.
Common recipes
A sensible project baseline
"permissions": {
"allow": [
"Bash(npm run test:*)",
"Bash(npm run lint:*)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"ask": [ "Bash(git push:*)" ],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
Lock down network access
"permissions": {
"allow": [ "WebFetch(domain:docs.anthropic.com)" ],
"deny": [ "WebFetch", "Bash(curl:*)", "Bash(wget:*)" ]
}
Deny beats allow — so this blocks all fetching except the allowed domain?
No: a bare WebFetch deny blocks everything, including the allow rule.
To allowlist domains, deny nothing and allow only the domains you want; unlisted
domains then fall back to prompting. Keep the curl/wget
denies either way — shell commands are the classic way around a WebFetch rule.
Environment variables
Everything under "env" is applied to every Claude Code session. Useful ones:
| Variable | What it does |
|---|---|
ANTHROPIC_MODEL | Model override (same effect as the model setting) |
BASH_DEFAULT_TIMEOUT_MS | Default timeout for shell commands Claude runs |
BASH_MAX_TIMEOUT_MS | Ceiling for timeouts Claude may request |
MAX_THINKING_TOKENS | Cap on extended-thinking budget |
MCP_TIMEOUT | MCP server startup timeout (ms) |
DISABLE_TELEMETRY | Set to 1 to opt out of telemetry (also disables feature-flag fetching) |
DISABLE_AUTOUPDATER | Set to 1 to disable auto-updates |
HTTP_PROXY / HTTPS_PROXY | Route traffic through a proxy |
All values must be strings — the builder above quotes them for you.
Hooks
Hooks run your own shell commands at lifecycle events — before a tool call
(PreToolUse), after one (PostToolUse), when Claude finishes
responding (Stop), and more. They live under the "hooks" key of
this same file. Designing matchers and commands is its own topic — use the
Hooks Builder, which walks through every event and emits a fragment
you can paste into the Hooks field above to merge into your full settings.json.
Top-level key reference
| Key | Type | What it does |
|---|---|---|
permissions | object | Allow/ask/deny rules, extra directories, default mode — see above |
env | object | Environment variables for every session |
hooks | object | Lifecycle-event shell commands — see the Hooks Builder |
model | string | Default model, e.g. "opus" or a full model ID |
statusLine | object | Custom status line: {"type": "command", "command": "…"} — see the Statusline Maker |
includeCoAuthoredBy | boolean | false removes the Co-Authored-By: Claude git-commit byline (default true) |
cleanupPeriodDays | number | How long chat transcripts are kept locally (default 30) |
apiKeyHelper | string | Script that prints an auth token — for short-lived credentials |
forceLoginMethod | string | Pin login to "claudeai" or "console" |
enableAllProjectMcpServers | boolean | Auto-approve every server in the project's .mcp.json |
enabledMcpjsonServers / disabledMcpjsonServers | array | Approve or reject specific .mcp.json servers by name |
outputStyle | string | Adjusts the system prompt via a named output style |
FAQ
- settings.json or CLAUDE.md — which one do I want?
settings.jsonconfigures the tool: hard rules the software enforces (permissions, env, hooks).CLAUDE.mdinstructs the model: conventions and context it reads as prose. A deny rule in settings.json is enforced; a "please don't" in CLAUDE.md is a request.- Do I need to restart Claude Code after editing?
- Start a new session to be sure. Settings are read at session start; a running session won't reliably pick up edits.
- Where do MCP servers go — settings.json?
- Server definitions live in
.mcp.json(project) or viaclaude mcp add. settings.json only controls approval of those servers (enableAllProjectMcpServersand friends) and per-tool permission rules (mcp__server__tool). - My allow rule isn't working.
- Three usual suspects: a deny rule somewhere in the stack (deny always wins, check
all three files plus managed policies), a missing
:*on a Bash rule you meant as a prefix, or a Read/Edit path anchored to the wrong base — remember/pathis settings-file-relative, not absolute. - Is it safe to commit .claude/settings.json?
- Yes — that's the point of the project scope. Keep personal machine paths and
experiments in
settings.local.json, which Claude Code adds to.gitignoreautomatically.