Settings, Permission Rules & Config

settings.json precedence and the rules that govern Claude

Advanced 13 minBuilder
What you'll be able to do
  • Recite the settings precedence chain from CLI flags down to managed policy and predict which value wins when two files set the same key
  • Write permission allow/ask/deny rules with correct pattern syntax and manage them with /permissions and /fewer-permission-prompts
  • Identify the high-value settings keys — autoUpdatesChannel, minimumVersion, claudeMdExcludes, autoMemoryEnabled, apiKeyHelper, hooks — and the env block (DISABLE_AUTOUPDATER, CLAUDE_CODE_GIT_BASH_PATH)
  • Use /config, /theme, and /color to set theme, editor mode, output style, and prompt suggestions interactively
  • Install and scope plugins (claude plugin install|remove|list, /plugins, --plugin-dir) and lock down an org with managed policy
At a glance

Claude Code's behavior is governed by a layered configuration system: settings.json files that merge along a strict precedence chain, permission rules that allow/ask/deny specific tools, and an env block plus interactive config for everything from themes to plugins. This lesson maps the whole stack — who wins when files disagree, the exact rule syntax, the keys that matter, and how to scope config per project, per user, and per org policy.

  1. 1The mental model: a stack of settings that merge
  2. 2The precedence chain (highest wins)
  3. 3Permission rules: allow, ask, deny
  4. 4Key settings and the env block
  5. 5Interactive config: /config, /theme, /color
  6. 6Plugins: marketplaces of skills, tools, MCP, and hooks
  7. 7Managed policy: org-wide control

The mental model: a stack of settings that merge

Everything Claude Code does is shaped by configuration — which model runs, whether it can push to git, what color the prompt is, which plugins load. That configuration does not live in one place. It lives in a stack of settings.json files plus a few command-line overrides, and they merge along a fixed precedence chain.

The key idea: this is not "one file wins." Each layer contributes keys, and when two layers set the same key, the higher-precedence layer wins for that key. Layers you didn't touch fall through to whatever the lower layers set. So your project's settings.json can pin a model while your user-level ~/.claude/settings.json still supplies your theme — they compose.

There are three reasons the layering exists, and they map onto who should control what:

  • Per-user preferences (~/.claude/settings.json) — your theme, your editor mode, defaults you want everywhere.
  • Per-project settings (.claude/settings.json, checked into the repo) — conventions the whole team shares: allowed commands, hooks, model.
  • Per-machine / personal-to-this-repo (.claude/settings.local.json, gitignored) — overrides just for you on this checkout, not shared with teammates.
  • Org policy (managed settings) — rules an administrator imposes that no lower layer can override.

Get the precedence chain in your head first; the rest of the lesson is detail hung on that spine.

Key insight

Merge, don't replace

A common mistake is assuming a project settings.json "resets" everything. It doesn't — it only overrides the specific keys it declares. Your user-level theme, env vars, and unrelated permission rules still apply unless a higher layer explicitly changes them. Configure the minimum at each layer and let the rest fall through.

The precedence chain (highest wins)

Here is the full chain, highest precedence at the top. When the same key is set in two places, the higher entry wins:

RankSourceScope / who controls it
1 (wins)CLI flags (--model, --permission-mode, --allowedTools, …)This one invocation only
2--settings ./file.json (file or inline)This one invocation; overrides matching keys
3project .claude/settings.local.jsonYou, on this checkout (gitignored)
4project .claude/settings.jsonThe team (checked into the repo)
5~/.claude/settings.jsonYou, everywhere (per-user)
6 (floor)Managed policy / enterprise managed settingsOrg admin — cannot be overridden

There is a deliberate twist at the bottom. Normally "higher wins," but managed policy is special: although it sits at the bottom of the merge, an administrator's managed settings are designed so the layers above cannot relax them. A managed deny rule stays denied even if your project tries to allow it. Think of managed policy as a hard floor the rest of the stack builds on, not a value you can shout over.

Walking the chain with an example — suppose all of these set a model:

text
CLI:                 claude --model opus        ← wins this run
--settings file:     { "model": "sonnet" }
settings.local.json: { "model": "sonnet" }
settings.json:       { "model": "haiku" }
~/.claude:           { "model": "sonnet" }

The session runs on opus — the CLI flag beats everything. Remove the flag and --settings wins; remove that and settings.local.json wins; and so on down. Each key resolves independently down this same ladder.

Tip

Where to put a setting

Team convention everyone needs → project .claude/settings.json (commit it). A tweak only you want on this repo → .claude/settings.local.json (gitignored). A preference you want in every project → ~/.claude/settings.json. A one-off for a single run → a CLI flag or --settings. Match the layer to the audience.

Permission rules: allow, ask, deny

The most consequential block in settings.json is permissions. It holds three lists — allow, ask, and deny — that govern what Claude may run, with pattern syntax targeting specific tools and commands.

json
{
  "permissions": {
    "allow": ["Bash(git status)", "Bash(git log *)", "Bash(npm test)", "Read"],
    "ask":   ["Bash(git push *)"],
    "deny":  ["Bash(rm -rf *)", "Read(./.env)", "Read(./secrets/**)"]
  }
}

What each list means:

  • allow — run without prompting. Use it for the safe, repetitive stuff (git status, git diff, npm test).
  • ask — always prompt, even if a mode would otherwise auto-approve. Use it to force a human checkpoint on sensitive-but-allowed actions.
  • deny — never run, no prompt. deny wins over everything — it overrides the active permission mode and any allow rule.

Pattern syntax. A rule is Tool(pattern):

PatternMatches
Read (bare tool)Every use of the Read tool
Bash(git status)Exactly git status
Bash(git log *)git log followed by anything (the * is a wildcard)
Read(./.env)Reading that specific file
Read(./secrets/**)Reading anything under secrets/ (recursive glob)

Manage all of this without hand-editing JSON using /permissions (alias /allowed-tools): it lists active rules and recent denials, and lets you turn a denial into a rule on the spot (press r to retry). And to generate a sensible allow-list automatically, run /fewer-permission-prompts — it scans your session transcripts for the safe, read-only commands you keep approving and proposes an allowlist to add to your project .claude/settings.json, cutting future prompts.

You can also allow-list for a single run from the CLI with the same rule syntax:

bash
claude --allowedTools "Bash(git log *)" "Bash(git diff *)" "Read"

Watch out

Keep allow rules narrow

A broad rule like Bash(git *) sweeps in git push and git reset --hard. Allow-list specific safe commands (Bash(git status), Bash(git log *)) rather than wildcarding a whole tool family. Narrow rules keep the dangerous variants prompting. And remember: anything that must never happen belongs in deny, not merely left out of allow.

Key settings and the env block

Beyond permissions, a handful of settings.json keys carry real weight. Here are the ones worth knowing by name:

KeyWhat it controls
autoUpdatesChannelRelease channel: latest (new features immediately) vs stable (~1 week delay, skips major regressions)
minimumVersionAn update floor — refuse to run below this version (useful for org consistency)
claudeMdExcludesGlob(s) of CLAUDE.md files to skip — invaluable in monorepos. (Managed-policy CLAUDE.md can't be excluded.)
autoMemoryEnabledToggle the auto-memory system (MEMORY.md); set false to disable
apiKeyHelperPath to a script that returns credentials dynamically (called every 5 min or on HTTP 401)
hooksShell commands wired to lifecycle events for guaranteed behavior (lint on save, block patterns, run tests at Stop)
permissionsThe allow/ask/deny block from the previous section
envA block of environment variables applied to the session

The env block is how you set environment variables declaratively in settings rather than exporting them in your shell:

json
{
  "env": {
    "DISABLE_AUTOUPDATER": "1",
    "CLAUDE_CODE_GIT_BASH_PATH": "C:\\Program Files\\Git\\bin\\bash.exe"
  }
}

Two env vars you'll reach for often:

  • DISABLE_AUTOUPDATER=1 — turns off only the background auto-update (use DISABLE_UPDATES=1 to stop all update paths). Handy on locked-down or offline machines, and it pairs naturally with minimumVersion for controlled rollouts.
  • CLAUDE_CODE_GIT_BASH_PATH — on Windows, points Claude Code at your Git Bash executable so the Bash tool works (otherwise it falls back to the PowerShell tool).

hooks deserve a special mention. Unlike CLAUDE.md instructions — which are advisory (Claude may or may not follow them) — a hook is a shell command Claude Code runs at a lifecycle point (PreToolUse, PostToolUse, Stop, SessionStart, and more). Hooks are deterministic and guaranteed. When you need an action to always happen, configure a hook in settings; don't merely ask in conversation.

Note

apiKeyHelper for dynamic credentials

apiKeyHelper points to a script that prints a credential to stdout — useful when your token rotates (gateways, short-lived creds). Claude Code calls it every ~5 minutes or whenever it gets an HTTP 401, and warns if the script takes over 10 seconds. It does not require you to hard-code a secret into settings.json.

Interactive config: /config, /theme, /color

Not every setting is something you want to edit by hand. The interactive layer covers the everyday cosmetic and ergonomic choices.

/config (alias /settings) opens the settings UI. From there you set:

  • Theme — light / dark / auto and accessibility variants.
  • Editor mode — toggle vim keybindings vs the default editor mode. (The old /vim command was removed; this lives in /config → Editor mode now.)
  • Output style — how Claude formats its responses.
  • Prompt suggestions — turn the suggestion hints on or off.
  • The release channel (latest vs stable) and model can also be chosen here.

/theme jumps straight to theme selection — auto, light, dark, colorblind-friendly (daltonized) variants, ANSI, or a custom theme from ~/.claude/themes/.

/color [color] sets the accent color of your prompt — a quick way to visually distinguish multiple concurrent sessions or environments (e.g., a red prompt for a production-adjacent checkout).

These interactive choices are written back into your settings files, so they persist across sessions and still obey the same precedence chain — a theme set in /config lands in ~/.claude/settings.json unless a higher layer overrides it.

Tip

Color-code dangerous sessions

Running several Claude Code sessions across different repos or against different environments? Give each a distinct prompt color with /color so you never fire a command at the wrong one. A glanceable visual cue beats reading the working-directory path every time.

Plugins: marketplaces of skills, tools, MCP, and hooks

Plugins are bundles of capability you install into Claude Code. A single plugin can package any mix of skills, tools, MCP servers, and hooks, and plugins are distributed through marketplaces — curated collections you install from.

Manage them from the CLI:

bash
claude plugin install code-review@claude-plugins-official   # install from a marketplace
claude plugin list                                          # list installed plugins
claude plugin remove code-review                            # uninstall

Inside a session, /plugins opens the plugin manager (browse, install, enable/disable) and /reload-plugins hot-reloads them after a change.

For local development or a one-off, load a plugin straight from a directory or .zip for the current session — no install required:

bash
claude --plugin-dir ./my-plugin     # repeatable; loads for this session only

This is the right tool for testing a plugin you're building, or pulling in a capability for a single run without committing to a global install. Because a plugin can carry MCP servers and hooks, installing one is a meaningful trust decision — it can wire commands into your lifecycle and connect external services. Install from marketplaces you trust, and review what a plugin bundles before enabling it.

Example

What lives in a plugin

A team's internal plugin might bundle: a /deploy-check skill, a custom MCP server for their issue tracker, a PreToolUse hook that blocks edits to infra/, and an output style. One claude plugin install brings all of it; claude plugin remove takes it all away — far cleaner than copying loose config files between machines.

Managed policy: org-wide control

The bottom of the precedence chain is reserved for administrators. Managed policy (also called enterprise managed settings) lets an organization impose configuration that the layers above cannot override — the one place where "lower in the chain" still means "final word."

Managed policy is delivered as a settings file (and a managed-policy CLAUDE.md) installed at OS-level system paths an ordinary user can't edit. Its purpose is governance: an admin can guarantee that across every developer's machine, certain commands are denied, a minimumVersion floor is enforced, auto-updates follow a chosen channel, or a particular CLAUDE.md is always loaded.

The critical properties:

  • A managed deny rule cannot be relaxed by a project allow rule or a user setting. Org-level prohibitions hold everywhere.
  • A managed-policy CLAUDE.md cannot be excluded by claudeMdExcludes — org instructions always load.
  • It composes with everything else: managed policy sets the floor, and the personal/project/CLI layers build on top of it for everything the policy leaves open.

This is how enterprises combine the flexibility developers need with the guarantees compliance requires: individuals still pick their theme, model, and project rules, while the organization keeps a non-negotiable safety and consistency baseline underneath.

Key insight

Two senses of "wins"

For ordinary keys, higher in the chain wins — a CLI flag beats a user setting. But managed policy inverts the intuition: it sits at the bottom of the merge yet is engineered so nothing above it can loosen it. Hold both ideas at once — higher layers win except against the guarantees an admin has locked in.

Try it: Layer and lock down config for a real repo

Take a project you work in and configure it through the full settings stack — proving you understand precedence, rules, and scope.

  1. See the chain in action. Set "model" to one value in your ~/.claude/settings.json and a different value in the project's .claude/settings.json. Start a session and run /config (or check the status line) to confirm the project value wins over the user value. Then launch once with claude --model <a-third-value> and confirm the CLI flag now wins.
  2. Build a permission profile. In project .claude/settings.json, add a permissions block: an allow list of safe commands (Bash(git status), Bash(git log *), Bash(npm test), Read), an ask entry (Bash(git push *)), and a deny block (Bash(rm -rf *), Read(./.env)). Open /permissions and verify the rules are active. Then ask Claude to push and confirm the ask/deny behavior fires regardless of mode.
  3. Auto-generate an allowlist. Run /fewer-permission-prompts, review what it proposes from your transcripts, and accept a narrow set into the project settings. Note how it targets read-only commands.
  4. Scope a personal override. Add a key (say a different autoUpdatesChannel or an env var) to .claude/settings.local.json and confirm it overrides the committed project value for you — and that it's gitignored so teammates aren't affected.
  5. Try the cosmetic layer. Use /config to flip editor mode (vim) and output style, /theme to switch theme, and /color to set a distinctive prompt color. Confirm the choices persist into a settings file.
  6. Reflect. Write a short paragraph: for one rule you added, which layer did you choose and why — would it have been wrong to put it one layer higher or lower? If you have org/admin access, note what you would push into managed policy so no developer could relax it.

Key takeaways

  1. 1Precedence (highest first): CLI flags > --settings > project .claude/settings.local.json > project .claude/settings.json > ~/.claude/settings.json > managed policy. Layers merge per-key; unset keys fall through.
  2. 2Permission rules use Tool(pattern) syntax in allow/ask/deny lists (e.g. Bash(git log *), Read(./secrets/**)); deny always wins; manage with /permissions and auto-generate a safe allowlist with /fewer-permission-prompts.
  3. 3Know the high-value keys: autoUpdatesChannel, minimumVersion, claudeMdExcludes, autoMemoryEnabled, apiKeyHelper, hooks, and the env block (DISABLE_AUTOUPDATER, CLAUDE_CODE_GIT_BASH_PATH).
  4. 4Use /config for theme, editor (vim) mode, output style, and prompt suggestions; /theme for themes; /color for prompt accent color — all written back into settings.
  5. 5Plugins bundle skills/tools/MCP/hooks from marketplaces: claude plugin install|remove|list, /plugins and /reload-plugins in-session, and --plugin-dir for a local/session-only load.
  6. 6Managed policy / enterprise managed settings sits at the bottom of the chain but cannot be overridden by higher layers — the place for org-wide deny rules, a minimumVersion floor, and a non-excludable CLAUDE.md.

Quiz

Lock in what you learned

Check your understanding

0 / 4 answered

1.Your project's `.claude/settings.json` sets `"model": "haiku"`. You launch with `claude --model opus`, and your `~/.claude/settings.json` sets `"model": "sonnet"`. Which model runs, and why?

2.Which permissions setup guarantees Claude can never push to git but can run `git status` without a prompt?

3.An administrator wants every developer's Claude Code to refuse a particular command and always load an org CLAUDE.md, with no way for individuals to override it. Where does this belong?

4.You're building a plugin locally and want to try it in your current session without installing it globally. Which is correct?

Go deeper

Hand-picked sources to keep learning