What this unit solves
settings.json is Claude Code’s behavioral control plane, governing tool permissions (allow / ask / deny), environment variables, hook mounts, and model selection. settings.local.json is the personal override layer that is never committed to version control, intended for local secret paths and individual preferences. Understanding the boundary between the two and their merge precedence keeps private key paths off git and prevents the common confusion of writing a deny rule that appears to do nothing. This unit provides copy-paste configuration snippets and a concrete path for verifying that a rule is actually in effect.Learning objectives
- Name the four areas
settings.jsoncovers (permissions / env / hooks / model) and give a concrete key-value example for each. - Distinguish
settings.json(team-shared, committed to version control) fromsettings.local.json(personal override, gitignored) by use-case. - Describe the merge precedence among user-level (
~/.claude/settings.json), project-level (.claude/settings.json), local (.claude/settings.local.json), and the managed enterprise layer. - Configure a deny list protecting
~/.ssh,**/.env, and similar sensitive paths, and verify the rules are actually in effect. - Explain the permission-range differences among the six permission modes (
default,acceptEdits,plan,auto,dontAsk,bypassPermissions) and choose the right one for a given task.
1. What settings.json does: four areas
settings.json is a structured JSON configuration file that Claude Code reads at startup to determine its own behavior. Its top-level keys are numerous — as of 2026-05 the official reference lists nearly a hundred, from cleanupPeriodDays to statusLine to outputStyle [1] — but in practice the ones you will actually touch fall into four areas:
permissions: tool permission rules (allow/ask/denyarrays), covered in depth in Section 4. This is the most important area of settings.json.env: environment variables injected into every session.hooks: custom commands mounted on lifecycle events (PreToolUse/PostToolUse/Stop, etc.); the mechanics are covered in 04-6.model: the default model, accepting aliases ("sonnet"/"opus"/"haiku") or a full model ID. The complete list of available models depends on your provider (Anthropic API, Bedrock, Vertex AI); the settings documentation itself does not enumerate them (as of 2026-05) [1].
2. settings.local.json: the personal override layer that stays out of version control
settings.local.json lives alongside settings.json in the .claude/ directory and is structurally identical. The single difference: it should never be committed to version control. The official documentation positions it as the gitignored local scope [1].
Typical uses are settings that are “valid only on your machine and should not be shared with the team”:
- Environment variables pointing to local secret paths or personal accounts.
- Extra deny rules you want personally but do not want to impose on everyone.
- Hooks that call tools that exist only on your machine.
settings.json (committed); anything that belongs only to your machine goes in settings.local.json (not committed).
3. Merge precedence: four layers plus the managed enterprise layer
When the same setting appears in multiple scopes, Claude Code applies them in order of precedence. The official precedence, from highest to lowest [1]:Managed (enterprise layer, highest)
Cannot be overridden by any other layer, including command-line arguments.
| Scope | Location |
|---|---|
| Managed | macOS /Library/Application Support/ClaudeCode/, Linux/WSL /etc/claude-code/, Windows C:\Program Files\ClaudeCode\ (or via plist / registry) |
| User | ~/.claude/settings.json |
| Project | .claude/settings.json |
| Local | .claude/settings.local.json (gitignored) |
4. permissions in practice: rule syntax and a baseline deny list
The rule format isTool or Tool(specifier). Evaluation order is deny, then ask, then allow; the first matching rule wins, so deny always takes precedence [2]. One detail worth memorizing: a bare tool name (e.g. Bash) used as a deny rule removes the entire tool from the model’s context — the model cannot see it at all. A rule with a specifier (e.g. Bash(rm *)) keeps the tool visible and blocks only the matching calls [2].
Specifier syntax by tool (as of 2026-05) [2]:
- Bash:
Bash(npm run build)is an exact match;Bash(npm run *)is a prefix match;*may appear anywhere (Bash(* install)matches anything ending withinstall). Whitespace determines word boundaries:Bash(ls *)matchesls -labut notlsof, whileBash(ls*)matches both. The:*suffix is equivalent to a trailing*. For compound commands separated by&&,||,;, or|, each segment must independently match a rule before the call is allowed. - Read / Edit: follow gitignore conventions. Four path anchors to keep straight:
| Pattern | Meaning | Example |
|---|---|---|
//path | Filesystem absolute path | Read(//Users/alice/secrets/**) |
~/path | Home directory | Read(~/.ssh/**) |
/path | Relative to project root (not absolute) | Edit(/src/**/*.ts) |
path or ./path | Relative to current directory | Read(.env) |
/Users/alice/file is not an absolute path — it is relative to the project root. Absolute paths require //Users/alice/file (two leading slashes) [2]. A bare filename follows gitignore semantics, so Read(.env) is equivalent to Read(**/.env) and matches .env at any depth.
- WebFetch:
WebFetch(domain:example.com). - MCP:
mcp__server(entire server),mcp__server__*,mcp__server__tool(single tool). - Agent:
Agent(Explore)controls available sub-agents.
A copy-paste baseline deny list (Windows-friendly)On Windows + PowerShell, two platform-specific details apply: Claude Code normalizes paths to POSIX form (This list blocks file reads of sensitive paths and dangerous commands, puts the irreversible
C:\Users\alice becomes /c/Users/alice), so matching .env across drives requires //**/.env. PowerShell rules use the PowerShell(...) prefix and follow the same shape as Bash rules [2].git push behind an ask, and lets everything else pass through the default. This is the principle of least privilege from 03-3 applied concretely.Bash(curl http://github.com/ *) is fragile — switching protocols, adding a redirect, or using a variable all bypass it. A more reliable approach is to deny curl, wget, and other network-capable Bash tools outright, and use WebFetch(domain:...) allowlisting instead. Alternatively, a PreToolUse hook can validate the URL before it runs. True OS-level blocking requires a sandbox [2]. Permissions and the sandbox are complementary layers: permissions block “what Claude attempts to access”; the sandbox blocks at the OS level “even if a prompt injection bypasses Claude’s judgment” (see 01-6).
Interactive rule evaluator
The evaluator below lets you try it directly: add rules, enter a tool call, and watch deny, ask, allow first-match-wins play out. The matching rule is highlighted in the verdict.5. Permission modes: six authorization tiers
The rules in Section 4 are per-call authorization. Permission modes are the overall baseline: they control whether each tool call pauses to ask you by default. The mode sets the baseline; the allow / ask / deny rules from Section 4 layer on top. As of 2026-06, Claude Code has six modes, arranged from tightest to most permissive by “what gets through without asking” [5]:| Mode | Auto-allowed | Typical use |
|---|---|---|
default | Reads only | Getting started, sensitive work; every action reviewed |
acceptEdits | Reads + file edits + common filesystem commands (mkdir / touch / rm / rmdir / mv / cp / sed) | Iterative coding where you review with git diff after the fact |
plan | Reads only (research and planning only, no file changes) | Mapping a codebase before touching it |
auto | Nearly everything, but each action passes a background safety classifier | Long tasks, reducing prompt fatigue |
dontAsk | Only pre-allowed tools and read-only Bash commands (everything else that would normally ask is rejected) | Locked-down CI and scripts |
bypassPermissions | Everything, including safety checks | Isolated containers / VMs only |
- Deny rules and explicit ask rules are enforced in every mode, including
bypassPermissions(allow rules become meaningless under bypass since everything passes through anyway). To hard-block an action, write a deny rule — do not rely on choosing a mode. - Writes to protected paths are never auto-allowed except under
bypassPermissions. Paths like.git,.claude(except.claude/worktrees),.vscode,.idea,.npmrc,.mcp.json,.claude.json, and shell rc files (.bashrc/.zshrc, etc.) are intercepted even if you haveacceptEditsactive or anEdit(.claude/**)allow rule in settings. This security check runs before allow rules [5]; see the official protected-paths section [5] for the full list. - Modes are a baseline only. The rules from Section 4 always layer on top — they are not mutually exclusive.
Switching modes
Three ways [5]:- Within a session:
Shift+Tabcyclesdefault -> acceptEdits -> plan.auto,bypassPermissions, anddontAskare not in the default cycle; they require separate activation (autorequires account eligibility;dontAsknever enters the cycle and is only activated via flag). - At startup:
claude --permission-mode plan(or any other mode name). - As the default: set
permissions.defaultModeinsettings.json.
The trap in three modes
auto, dontAsk, and bypassPermissions each carry a specific misread worth addressing separately.
auto (requires v2.1.83+) is not “automatic yes” — it is “classifier blocks high-risk, rest passes through.” An independent classifier model reviews each action before it runs, blocking actions that exceed the scope of your request, reach unknown infrastructure, or appear to be driven by malicious content. curl | bash, production deployments, force pushes to main, and irreversible deletion of existing files are all blocked by default [5]. Two behaviors you need to know: conversation-level boundaries you state (“do not push yet”, “wait until I review”) are treated as block signals by the classifier, but those boundaries live in the transcript only — once context compression removes that sentence, the boundary is gone. For a hard guarantee, use a deny rule. Also, defaultMode: "auto" in a project-layer or local-layer file is silently ignored (v2.1.142+) to prevent a repo from granting itself auto mode; this setting can only be placed in ~/.claude/settings.json [5].
dontAsk is “automatic rejection,” not “automatic approval.” The name is easy to misread: it rejects all calls that would normally prompt for confirmation, leaving only tools matching your permissions.allow and read-only Bash commands. Even ask rules are rejected outright rather than prompted [5]. This mode is for CI where you have already precisely defined what Claude is allowed to do; anything not pre-allowed is silently refused.
bypassPermissions disables safety checks and should only be used in isolated environments. It allows writes to protected paths (from v2.1.126), removing all safety nets. Only explicit ask rules and rm -rf / / rm -rf ~ (filesystem root / home deletions, the last circuit breaker) still block [5]. On Linux / macOS, starting as root or with sudo is rejected outright. The official documentation is unambiguous: it provides zero protection against prompt injection; use auto instead when the goal is “fewer interruptions with some protection” [5]. --dangerously-skip-permissions is this mode; the name is instructive.
Same task, which mode?You want Claude to refactor a module into multiple files, which means many file writes, but you want to keep review authority:
default: every write pauses for confirmation — safe, but you will be interrupted dozens of times.acceptEdits: writes proceed without interruption; onegit diffafter the run covers everything. Writes outside the working directory, protected paths, and other Bash commands still ask. This is the right choice for most “I will review after” scenarios.bypassPermissions: even protected paths are not blocked. Unless you are in a throwaway container, this trades reproducibility and safety for convenience at an unfavorable ratio.
acceptEdits. Need to review every step — use default. Need a long unattended run with some protection — use auto (in an isolated or low-risk repo). bypass belongs only in environments where “even if a prompt injection takes over, it cannot reach anything I care about.”6. How settings.json and claude.json divide the work
One sentence:settings.json owns policy (behavioral settings you edit), while claude.json owns state (internal state Claude Code maintains itself, such as per-project history and trust records). You should not manually edit claude.json — it is not a human-authored file. Its correct path, content format, and the question of whether to touch it at all are covered in 02-4.
Tool comparison
The concept of a “project-level behavioral configuration file” exists across tools, but maturity and granularity vary significantly (as of 2026-05; exact formats are subject to each vendor’s current documentation):| Concept | Anthropic Claude (primary) | OpenAI (Codex) | Google (Gemini CLI) | GitHub Copilot | Cursor |
|---|---|---|---|---|---|
| Project-level config file | .claude/settings.json [1] | .codex/config.toml (project root down, closest wins) + AGENTS.md [3] | .gemini/settings.json [4] | .github/copilot-instructions.md (instructions, not permissions) | .cursor/rules/*.mdc + GUI settings |
| Personal override (not in VCS) | .claude/settings.local.json [1] | Needs source verification | Needs source verification | Needs source verification | Needs source verification |
| User-global config | ~/.claude/settings.json [1] | ~/.codex/config.toml [3] | ~/.gemini/settings.json [4] | VS Code user settings | User Rules (GUI) |
| Tool permission control (allow / deny) | permissions.allow / deny / ask arrays [2] | approval_policy / sandbox_mode in config.toml [3] | settings.json includes tool permissions [4] | No equivalent fine-grained tool permissions | Needs source verification |
| Hook mount points | hooks.PreToolUse / PostToolUse / Stop [1] | Needs source verification (no direct equivalent) | Needs source verification | No direct equivalent | No direct equivalent |
The comparison table gives coordinates, not detailsExact mechanisms and paths for each cell are fast-moving facts; entries that cannot be confirmed are marked “needs source verification” — refer to each vendor’s official documentation. Fine-grained, machine-enforced tool permissions (as opposed to natural-language instructions) are most mature in Claude Code’s
permissions and Codex’s sandbox / approval policy. Copilot and Cursor project files are closer to “instruction / rule” artifacts that rely on model compliance rather than hard enforcement boundaries.Hands-on exercises
Add the baseline deny list and verify it is actually in effect
Add the baseline deny list above to your project’s
.claude/settings.json, then take two steps to confirm the rules are working rather than just present:Run /permissions inside Claude Code. This UI lists all merged rules and which settings file each one came from — you can see directly whether the deny rules made it into the final policy [2].Probe it: ask Claude to read a path that is denied (for example, a file under ~/.ssh/), and confirm it is blocked rather than read. Remember you are verifying the merged final policy, not a single settings file.Create settings.local.json with a personal variable and confirm it is not tracked by git
Create If the filename comes back, the file is already tracked. Run
.claude/settings.local.json with an env variable that belongs only to your machine, then verify it is gitignored:git rm --cached .claude/settings.local.json immediately and add the pattern to .gitignore.Common pitfalls
Self-check
The bar for passing this unit
- Can you explain in one sentence the division of labor between
settings.json(policy, machine-enforced) andCLAUDE.md(rules, the model may not follow)? To block a dangerous action, which file do you write in? - Does your project
.gitignoreexcludesettings.local.json? Doesgit ls-files .claude/settings.local.jsonreturn empty? - Given the rule
Read(/Users/alice/secret), does it block an absolute path or a path relative to the project root? How would you write it to block the absolute path? - Is your current set of active deny rules something you know from looking at a single settings file, or from reading the merged final policy with
/permissions? - The user layer has an
allowrule; the project layer has adenyfor the same action. Is the action ultimately allowed or blocked? - Does
dontAskmode automatically allow actions that would normally prompt, or automatically reject them? What is the fundamental difference betweenbypassPermissionsandauto?
Sources and further reading
Factual claims are grounded in official documentation; fast-changing items are annotated as of 2026-05 (the permission modes section is annotated as of 2026-06).- [1] Anthropic, “Claude Code settings,” Claude Code Docs. (settings.json top-level key reference; four-layer plus managed precedence Managed -> CLI args -> Local -> Project -> User; permissions merge across scopes rather than override; model key accepts aliases and full IDs) https://code.claude.com/docs/en/settings (as of 2026-05)
-
[2] Anthropic, “Configure permissions,” Claude Code Docs. (rule syntax
Tool(specifier); evaluation order deny -> ask -> allow first-match-wins; a deny in any layer cannot be lifted by an allow in another; Read / Edit gitignore path anchors and Windows POSIX normalization; permissions enforced by Claude Code not the model;/permissionsto inspect merged rules; permissions and sandbox as complementary layers) https://code.claude.com/docs/en/permissions (as of 2026-05) -
[3] OpenAI, “Codex configuration,” OpenAI Developers Docs. (user-global
~/.codex/config.tomland project.codex/config.toml(project root down, closest wins);approval_policy/sandbox_mode;AGENTS.mdas Codex project instructions equivalent to CLAUDE.md) https://developers.openai.com/codex/config-reference (as of 2026-05) -
[4] Google, “Gemini CLI configuration,” Gemini CLI Docs. (user
~/.gemini/settings.jsonand project.gemini/settings.json, project overrides user; settings.json controls tool permissions, MCP servers, workspace, etc.) https://geminicli.com/docs/reference/configuration/ (as of 2026-05) -
[5] Anthropic, “Choose a permission mode,” Claude Code Docs. (six modes default / acceptEdits / plan / auto / dontAsk / bypassPermissions and their auto-allow scopes; Shift+Tab cycling and
--permission-modeflag,permissions.defaultMode; deny and explicit ask rules enforced in every mode; protected paths not auto-allowed except under bypass; auto background classifier, requires v2.1.83+, conversation boundaries lost on context compression,defaultMode: autoignored in project/local layers; dontAsk auto-rejects; bypassPermissions disables safety checks, root/sudo refused,disableBypassPermissionsMode/disableAutoModemanaged locks) https://code.claude.com/docs/en/permission-modes (as of 2026-06)