DEV Community

Claude Code settings precedence: which of your five settings files actually wins

You set permissions.defaultMode in one settings file, restart Claude Code, and a different mode is active. Nothing is broken - another file won. Claude Code reads settings from up to five places, and when the same key appears in more than one, a fixed precedence decides which value applies. Most "my setting doesn't work" reports are really precedence questions. Here's the whole model, verified against the official settings docs as of Claude Code 2.1.22x. The five layers From highest priority to lowest: - Managed settings - deployed by an organization (server-delivered, MDM/OS policy, or a system-level managed-settings.json ). Nothing below can override these, with a handful of deliberate security exceptions I'll get to. - Command line - JSON passed via --settings . Temporary, session-scoped. - Local project settings - .claude/settings.local.json at the repository root. Yours, this repo only. - Shared project settings - .claude/settings.json , checked into the repo. The team's. - User settings - ~/.claude/settings.json . Yours, every project. If your user file sets spinnerTipsEnabled: true and the project file sets it to false , the project value applies. That's the whole rule for scalar values: higher layer wins, key by key. A key you don't set in a higher layer falls through to the lower one - layers merge, they don't replace each other wholesale. The part that surprises people: arrays merge, they don't override Scalar values override. Array values concatenate and deduplicate across scopes. That includes the arrays people care most about: permissions.allow , permissions.deny , sandbox.filesystem.allowWrite , and friends. If the shared project file allows Bash(npm test:) and your local file allows Bash(pnpm test:) , both rules are active. Lower-priority scopes can add entries; they cannot remove entries a higher scope contributed - and vice versa. Two practical consequences: - You can't "turn off" a team permission rule from your local file. There is no negative entry. If a checked-in allow rule is too broad for your taste, that's a conversation with your team, not a local override. - Permission problems are often additive surprises: a rule you forgot in your user file is still merging into every project. Two arrays are exceptions and do not merge this way: fallbackModel is an ordered chain, so the highest-precedence file that defines it supplies the entire list; and a managed availableModels allowlist applies as-is - user, project, and local entries can't extend it. Where each rule belongs A decision rule that has held up well: | You're setting... | Put it in | |---|---| | Personal defaults you want everywhere (theme, editor, your own allow rules) | ~/.claude/settings.json | | Anything the team should share (project permissions, hooks, env) | .claude/settings.json (checked in) | | Personal experiments, machine-specific paths, permissions you're trialing | .claude/settings.local.json | | One-off session behavior | --settings on the CLI | The test for "shared vs local" is: would a teammate's session be wrong without this? If yes, it's .claude/settings.json . If it only makes your machine work, it's local. Two gotchas in the local file The auto-gitignore only happens when Claude Code writes the file. When Claude Code itself saves a setting into .claude/settings.local.json in a repo that doesn't already ignore it, it adds **/.claude/settings.local.json to your global git excludes. If you create the file by hand, that step never runs - add it to your gitignore yourself, or you'll commit your personal settings to the team repo. Its location changed in v2.1.211. The file now belongs at the repository root. Older versions wrote it into whatever directory you started Claude Code from, and current versions still read a file an older version left there. When both exist and set the same key, the repo root's value wins - except permission rules, which follow the array rule above: rules from both files stay in effect. The security exceptions A few security-sensitive keys deliberately break the "higher layer wins" rule, in the restrictive direction only: - disableClaudeAiConnectors: true applies from any scope, even when a managed source saysfalse . - crossSessionInbound (how sessions accept messages from other sessions) uses anaccept < hold < refuse ladder, and a stricter value in project or local settings beats managed and user values. A checked-inaccept can never loosen your personalrefuse . The design intent is consistent: a random file in a cloned repo can tighten your security posture, but it can never loosen it. How to see what actually loaded Don't reason about precedence from memory - inspect it: - /status โ†’ the Status tab has aSetting sources line listing every layer loaded this session (User settings ,Project local settings ,Enterprise managed settings (remote) , ...). A layer only appears if it loaded with at least one key. Note it shows which sources loaded, not which layer supplied each individual key. - claude doctor โ†’ details when a settings file fails to parse or validate. An interactive session also shows a Settings Error dialog at startup and lets you continue without the broken file - which is its own trap: your session then runs without settings you believe are active. The mental model in four lines - Five layers; higher wins per key; managed is (almost) absolute. - Scalars override. Arrays merge - you can add, you can't subtract. - Local is for you, project is for the team, user is for every repo. - When in doubt, /status beats guessing. I maintain Rulestack - ready-to-fork rule packs, skills, and hooks for Claude Code, Cursor, and Codex. The precedence notes above come from keeping those packs verified against each release. Daily notes on AI coding agents on Bluesky: @ai-shop.bsky.social Top comments (0)

Comments

No comments yet. Start the discussion.