Explainer
Which config file actually wins?
You wrote a rule in one file. The agent is behaving as though you wrote it in another. Somewhere between
your home directory, your repo, and a managed policy your IT department installed, one of those files won
— and the reason this is hard to reason about is that the direction of precedence is not the same
for every mechanism. Settings files let the repo beat your laptop. Skills let your laptop beat the
repo. CLAUDE.md lets nothing beat anything. Permission rules refuse to pick a winner at all
and merge instead. Four mechanisms living in the same .claude/ directory, four different
answers.
This is the reference for all of them, plus the equivalents in Codex CLI, Cursor and Gemini CLI, which
disagree with Claude Code and with each other. Every row is sourced, and the rows we could test in a
sandbox are marked as tested with the observed output. Everything for Claude Code was checked against
v2.1.229 against the docs at code.claude.com on August 13, 2026;
the cross-tool rows cite Codex CLI 0.147.0, the Cursor agent CLI build
2026.08.11 and Gemini CLI v0.55.1.
The one-screen answer
| Mechanism | Resolution (winner first) | Merge or replace? |
|---|---|---|
settings.json | managed → CLI args → local → project → user | Scalars replace, arrays concatenate |
| Permission rules | deny → ask → allow, from any scope | Merge across every scope |
CLAUDE.md | no winner — all files load together | Concatenate |
.claude/rules/ | project rules over user rules | Concatenate, project last |
| Skills and commands | enterprise → personal → project | Replace by name |
| Subagents | managed → --agents → project → user → plugin | Replace by name |
| MCP servers | local → project → user | Replace whole entry by name |
| With Backgrind | Backgrind wraps the CLI you already run, so this table is your config — the overlay adds no layer of its own to resolve against. Its own approval rules live in a separate file the agent never reads. | |
Read rows one, five and six together and the practical rule falls out: a committed setting is
policy, a committed skill is only a default. A teammate can shadow your project's
/deploy with a personal one and never see a warning, but they cannot loosen a permission you
denied.
rules/ directory sitting beside them, and to the settings.json one level up.1. Settings files: highest scope wins, and it is not your laptop
settings.json is the only mechanism that behaves the way people expect a config system to
behave. Claude Code applies scopes highest-first:
| Rank | Scope | Where |
|---|---|---|
| 1 | Managed / policy | macOS /Library/Application Support/ClaudeCode/managed-settings.json; Linux and WSL /etc/claude-code/; Windows C:\Program Files\ClaudeCode\ and HKLM\SOFTWARE\Policies\ClaudeCode |
| 2 | Command line arguments | --settings, --model, --allowedTools, … |
| 3 | Local | .claude/settings.local.json — at the git repository root since v2.1.211 |
| 4 | Project | .claude/settings.json |
| 5 | User | ~/.claude/settings.json |
The load-bearing line is rank four beating rank five: the committed project file overrides every
developer's personal file. That is what makes .claude/settings.json a policy surface.
Rank three is the catch — a developer's own settings.local.json overrides the project file, so
that file is personal scratch space and belongs in .gitignore, never in a team setup. Our
team setup guide covers what to commit and what to keep out.
We confirmed the direction rather than trusting it. In a scratch repo with "model": "haiku" in
.claude/settings.json and "model": "sonnet" in
.claude/settings.local.json, over a user file that specifies a third model entirely:
$ claude -p "Reply with exactly: OK" --output-format json | jq '.modelUsage | keys'
[ "claude-sonnet-5" ] # local beat project
$ claude -p "…" --setting-sources user,project --output-format json | jq '.modelUsage | keys'
[ "claude-haiku-4-5-20251001" ] # drop local, project beats user
Two details that bite once you go past a single scope. First, within the managed tier the sources
do not merge — Claude Code picks exactly one and ignores the rest, in the order policy helper
output, remote server-managed settings, MDM policies, the files in
managed-settings.d/ plus managed-settings.json, then the Windows
HKCU registry. Second, the legacy Windows path
C:\ProgramData\ClaudeCode\managed-settings.json stopped being supported in v2.1.75, which is a
fun way to have an enterprise policy silently stop applying.
Managed settings are documented as unoverridable "including command line arguments" — with a short,
explicit exception list, all of which go one way, towards more restriction: a true for
disableClaudeAiConnectors or isolatePeerMachines applies from any scope even when a
managed source says false, a false for remoteControlAtStartup in
project or local settings applies even when managed says true, and a stricter
crossSessionInbound value wins over a looser managed one. You can always tighten. You can never
loosen.
The trust gate nobody mentions until it bites
A committed permissions.allow array is not applied on clone. permissions.allow and
permissions.additionalDirectories from a project's .claude/settings.json grant
capability, so they only take effect after you accept the workspace trust dialog for that folder. In a
sandbox repo we never trusted, Claude Code told us so on stderr and carried on without it:
Ignoring 1 permissions.allow entry from .claude/settings.json: this workspace
has not been trusted. Run Claude Code interactively here once and accept the
trust dialog, or set projects["…/proj"].hasTrustDialogAccepted: true in
/Users/you/.claude.json.
Note what was not ignored in the same run: an allow entry in
.claude/settings.local.json, which is your own file and therefore not gated, and the
deny array in the project file, because a deny only restricts. This also means a cloned repo
cannot silently widen anyone's permissions, which is the actual reason the gate exists.
What merges and what replaces
| Key shape | Across scopes | Confidence |
|---|---|---|
Scalars (model, theme) | Higher scope replaces | Documented + tested |
Arrays (permissions.*, sandbox.filesystem.allowWrite) | Concatenated and de-duplicated, not replaced — a lower scope can add entries | Documented |
hooks | Merge across levels; identical handlers defined twice run once | Documented |
fallbackModel | Exception: the highest scope that defines it supplies the whole ordered chain | Documented |
availableModels | Exception: a managed list applies as-is and cannot be extended | Documented |
Objects (env, statusLine) | Not stated for user/project/local. Deep merge is documented only inside managed-settings.d/, and per-key env merging only across admin-controlled managed sources (v2.1.223+) | Gap in the docs |
If you are relying on a partial env block in a project file topping up a user one, you are
relying on undocumented behaviour. Write the whole object in one place.
2. Permission rules: deny wins from anywhere
Permission rules are the mechanism that refuses to have a precedence order between scopes. They merge across managed, CLI, local, project and user, and then evaluate in a fixed sequence: deny, then ask, then allow. The first match in that order decides, and specificity does not change the order.
Two consequences, and both are counter-intuitive the first time:
- A deny at any level cannot be overridden by an allow at any other. A project deny blocks a
user allow; a user deny blocks a project allow. We put
"deny": ["Bash(sw_vers:*)"]in the project file and"allow": ["Bash(sw_vers:*)"]in the personalsettings.local.json— nominally the higher scope — and asked for the command. The result was a recorded permission denial and the reply "The command was denied — permission to runsw_versvia Bash was not granted, so it did not execute." - A broad deny cannot carry allowlist exceptions.
Bash(aws *)blocksaws s3 lseven withBash(aws s3 ls)inallow. Write denies narrowly or you will block work you meant to permit.
Hooks sit beside this, not above it: a hook returning allow does not bypass a matching deny
rule, and a matching ask rule still prompts. The one hook that outranks the rules is one that exits with
code 2, which stops the call before permission evaluation. For shaping the ask list so an agent
runs long stretches unattended, see
pre-approval policies.
3. CLAUDE.md: there is no winner, and that is the answer
The most-searched precedence question about Claude Code has an anticlimactic answer.
All discovered CLAUDE.md files are concatenated into context rather than overriding
each other. The table everyone quotes is a load order from broadest scope to most specific, not a
ladder:
| Order | Scope | Location |
|---|---|---|
| 1st | Managed policy | macOS /Library/Application Support/ClaudeCode/CLAUDE.md; Linux and WSL /etc/claude-code/CLAUDE.md; Windows C:\Program Files\ClaudeCode\CLAUDE.md |
| 2nd | User | ~/.claude/CLAUDE.md |
| 3rd | Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
| 4th | Local | ./CLAUDE.local.md, appended after CLAUDE.md in the same directory |
Later means "read last", not "wins". The docs are unusually blunt about the consequence: if two rules
contradict each other, Claude may pick one arbitrarily, and these files are
context, not enforced configuration. An engineer with "always use npm" in
~/.claude/CLAUDE.md working in a repo whose CLAUDE.md says "always use pnpm" gets a
coin flip, not an error. If the rule genuinely must hold, it belongs in a deny rule or a
PreToolUse hook. The one exception is the managed policy file, which cannot be excluded by
claudeMdExcludes; there is also a claudeMd key that inlines the same content, and it
is honoured in managed and policy settings only — setting it in user, project or local settings does nothing.
Across a directory tree, and below it
Claude Code walks up from your working directory, so launching in foo/bar/ loads
foo/CLAUDE.md before foo/bar/CLAUDE.md. Files below your working directory
behave differently: they are not loaded at launch, but injected when Claude reads a file in that
subdirectory with the Read tool. We watched it happen. With sub/CLAUDE.md defining a value and
a plain text file beside it:
# run 1 — never touch sub/
SUB_WORD=UNKNOWN
# run 2 — Read sub/note.txt first, then ask
"an extra instruction file was injected into my context after that read:
a <system-reminder> containing the contents of sub/CLAUDE.md"
That is worth internalising for monorepos: a package-level CLAUDE.md is not in context while the
agent plans, only once it opens a file there. It also does not survive compaction — the project-root file is
re-injected, nested files and rules with paths: frontmatter are not.
Imports
@path imports are expanded at launch, recursively, to a maximum depth of four
hops. Relative paths resolve against the file containing the import, not your working directory, and
home paths like @~/.claude/notes.md work. Import parsing skips Markdown code spans and fenced
blocks, so `@README` in backticks stays literal. One security detail added recently: an import
in a project-level file whose path resolves outside your working directory triggers a one-time
approval dialog listing the files; decline and those imports stay off. User-scope imports load without the
dialog.
Imports do not save context — the content is expanded into the prompt either way. Splitting a large file into imports buys organisation, not tokens.
4. .claude/rules/: the direction flips back
Rules are the path-scoped sibling of CLAUDE.md, and they are in the official docs, not folklore.
All .md files under .claude/rules/ are discovered recursively. A rule with no
paths frontmatter loads at launch with the same priority as .claude/CLAUDE.md;
a rule with a paths: glob list triggers when Claude reads a matching file, not on every tool call.
And here is the precedence sentence that catches people: user-level rules in
~/.claude/rules/ load before project rules, giving project rules higher priority. That
is the opposite direction to skills, which live one directory away. Two adjacent mechanisms, opposite
defaults.
We verified rules load at all, which is the part people doubt. In our sandbox, a
.claude/rules/precedence.md with no frontmatter was quoted back correctly at launch, alongside
CLAUDE.md, CLAUDE.local.md and an @-imported file — while
AGENTS.md in the same directory was not:
PROJECT_WORD=BRAVO # ./CLAUDE.md loaded
IMPORT_WORD=GOLF # @imported.md loaded
LOCAL_WORD=FOXTROT # ./CLAUDE.local.md loaded
RULES_WORD=ECHO # .claude/rules/*.md loaded
SUB_WORD=UNKNOWN # sub/CLAUDE.md not loaded at launch
AGENTS_WORD=UNKNOWN # ./AGENTS.md never loaded 5. Skills, commands and subagents: three files, two directions
Skills are the trap. When skills share a name across levels, enterprise overrides personal, and
personal overrides project — so a deploy skill in ~/.claude/skills/ runs
instead of your repo's. A skill at any of those levels also overrides a bundled one, and if a skill and a
command in .claude/commands/ share a name, the skill takes precedence. Nested skills are the
exception that does not conflict: a skill in apps/web/.claude/skills/ appears as
/apps/web:deploy and both stay available.
Subagents then reverse it:
| Priority | Subagent source | Scope |
|---|---|---|
| 1 (highest) | Managed settings | Organization-wide |
| 2 | --agents flag | Current session |
| 3 | .claude/agents/ | Current project |
| 4 | ~/.claude/agents/ | All your projects |
| 5 (lowest) | Plugin agents/ | Where the plugin is enabled |
So .claude/agents/reviewer.md beats ~/.claude/agents/reviewer.md, while
.claude/skills/reviewer/ loses to ~/.claude/skills/reviewer/. Same repo, same home
directory, opposite outcomes, and nothing in the session announces which one it picked. Nested project
agents resolve to the definition closest to the working directory as of v2.1.178 — but two files declaring
the same name under one .claude/agents/ tree is explicitly undefined behaviour:
the docs say the choice is made "by filesystem read order rather than a documented precedence", and
/doctor reports the collision.
One more asymmetry worth knowing: --add-dir loads .claude/skills/ from the added
directory, while permissions.additionalDirectories grants file access only and loads nothing.
6. MCP servers: three scopes, no field merging
When the same MCP server name is defined in more than one place, Claude Code connects once, using the entire entry from the highest-precedence source — fields are not merged across scopes. The order is local, then project, then user.
| Scope | Stored in | Shared with the team |
|---|---|---|
| Local (wins) | ~/.claude.json, scoped to the current project | No |
| Project | .mcp.json at the repository root, not inside .claude/ | Yes, via version control |
| User | ~/.claude.json | No |
Three gotchas the docs call out and everyone hits anyway. settings.json does not read an
mcpServers key at all, so putting one there does nothing. The three scopes match duplicates by
name, but plugins and connectors match by endpoint, so a plugin pointing at the same URL is treated
as a duplicate of a server above it. And project-scoped servers normally require a one-time approval — but
claude -p runs, Agent SDK sessions and cloud sessions cannot show that prompt, so they load
project-scoped servers without asking. That is a supply-chain edge worth knowing before you wire a repo's
.mcp.json into CI.
7. Across tools, nothing agrees
If you run more than one agent — and most people now do — the file you wrote for one is probably invisible to another. This is the table to keep:
| Tool | Instruction files read | Conflict direction |
|---|---|---|
| Claude Code 2.1.229 | CLAUDE.md, CLAUDE.local.md, .claude/rules/. Not AGENTS.md | No override — everything concatenates |
| Codex CLI 0.147.0 | AGENTS.override.md then AGENTS.md, at ~/.codex/ and in every directory from project root down to cwd | Closest to cwd wins, because it is concatenated last |
| Cursor (agent 2026.08.11) | .cursor/rules/*.mdc, plus AGENTS.md and CLAUDE.md automatically | Team rules → project rules → user rules; earlier wins |
| Gemini CLI 0.55.1 | GEMINI.md only, unless you set context.fileName | Sub-directories → workspace root → extensions → global |
Four specifics behind that table, because each is a real trap:
- Claude Code does not read AGENTS.md. Tested above, and stated in the docs in as many
words. The documented fixes are a leading
@AGENTS.mdimport inCLAUDE.md, or symlinking the two files. Our CLAUDE.md vs AGENTS.md guide covers what belongs in each. - Codex has an override filename most people have never seen. In each directory it checks
AGENTS.override.mdfirst, thenAGENTS.md, then anything inproject_doc_fallback_filenames— one file per directory, at most. A stray override anywhere on the path from repo root to your cwd silently wins. The combined chain is capped byproject_doc_max_bytes, 32 KiB by default, and the docs contradict themselves on whether that cap is per-file or for the whole chain, so treat it as "somewhere around 32 KiB and don't find out". - Cursor reads CLAUDE.md, and always applies it. Alone among the four, it special-cases
Anthropic's file:
CLAUDE.mdis applied to every conversation regardless of anyalwaysApplyfrontmatter. Note also that a plain.mdfile dropped into.cursor/rules/is ignored — only.mdccounts there. - Gemini CLI still does not read AGENTS.md by default. We re-checked the claim from our
Gemini CLI install guide against the v0.55.1 source rather than
repeating it:
DEFAULT_CONTEXT_FILENAMEis the single string'GEMINI.md', thecontext.fileNamesetting defaults to undefined, and the open issue asking for AGENTS.md parity is still open with several attempted pull requests closed unmerged. Opt in with{"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}}— and note that key has no merge strategy, so a workspace array replaces the user's wholesale rather than unioning with it.
The reversal you will not expect: Gemini's settings order
Claude Code puts the managed policy file at the top of the list. Gemini CLI puts two system files at
opposite ends of it. Its documented order, lowest first, is: built-in defaults, the system defaults file,
user settings, workspace settings, the system settings file, environment variables, command line arguments.
Read that again — system-defaults.json is the weakest layer, and settings.json in
the same system directory is the strongest of the files. On macOS both live under
/Library/Application Support/GeminiCli/, not /etc/. Gemini also deep-merges rather
than replacing, with per-key array strategies: mcpServers shallow-merges,
tools.exclude unions (so you cannot un-exclude a tool at a lower layer), hooks concatenate, and
everything without a declared strategy replaces.
Codex resolves its config.toml highest-first as CLI flags and -c overrides, project
.codex/config.toml files from repo root down to cwd (closest wins, trusted projects only), the
profile file selected by --profile, ~/.codex/config.toml, then
/etc/codex/config.toml. It also maintains a blocklist of keys that are ignored in a
project-local config with only a startup warning — model_providers, notify,
profile, otel and others — which is a precedence rule dressed as a security
control. And an untrusted project skips every project-scoped .codex/ layer, config, hooks and
rules alike, while user-level ones keep loading.
8. What happens on a tie
| Situation | Outcome |
|---|---|
Two CLAUDE.md files contradict each other | Undefined. The docs say Claude "may pick one arbitrarily" |
A rule matches both deny and allow | Deny. Always, from any scope, regardless of specificity |
| A skill and a slash command share a name | The skill wins |
Two subagent files in one .claude/agents/ tree share a name | Undefined — filesystem read order. /doctor flags it |
| Two Cursor rules share a filename in different folders | Both apply. Cursor identifies rules by full path, so there is no override |
The same key is set in settings.local.json at the repo root and in a subdirectory | Repository root wins — except permission rules, where both stay in effect |
9. Debugging "why is my setting ignored"
Work in this order. It resolves almost every case in under a minute.
| Command | Answers |
|---|---|
/status | Which setting sources are active at all, including whether managed settings are in effect |
/permissions | The resolved allow, ask and deny rules and the settings file each one came from. The only fully attributed view |
/context | What is actually occupying the window: memory files, skills, MCP tools, and subagents with the source each loaded from |
/memory | The memory file locations across user and project scopes |
/doctor, claude doctor | Invalid settings files, duplicate subagent names, unused extensions. The CLI form prints diagnostics without starting a session |
claude --setting-sources user,project | Bisect by hand: drop a layer and see whether the behaviour changes |
claude --safe-mode | All customizations off — CLAUDE.md, skills, plugins, hooks, MCP, commands. Managed policy still applies |
CLAUDE_CONFIG_DIR=/tmp/clean claude | A clean-room config directory, for when you suspect your own home directory |
InstructionsLoaded hook | Logs exactly which instruction files load, when, and why — matchers include session_start, nested_traversal, path_glob_match, compact |
The honest gap: no command prints a fully merged effective settings.json. The
docs say so directly — the "Setting sources" line confirms which sources are read, but "does not show which
layer supplied each individual key", and the Config tab is an editor for a fixed set of toggles, not a view
of your settings. /permissions is the one surface that resolves and attributes. For everything
else, bisect with --setting-sources. In Gemini CLI, /memory list prints the paths of
the context files in use and /memory show prints the concatenated result, which is the closest
equivalent any of these tools offers.
10. Confidence: what we tested, and what we only read
A wrong precedence claim in a reference post gets copied into other people's repositories, so here is the ledger. Everything in the "tested" column was reproduced in a scratch git repository with Claude Code v2.1.229 on macOS on August 13, 2026.
| Claim | Evidence |
|---|---|
local > project > user for settings.json | Tested — three conflicting model values, resolved model read from --output-format json |
| deny beats allow across scopes | Tested — project deny vs local allow, tool call denied |
Project permissions.allow is trust-gated | Tested — stderr warning, quoted above |
Claude Code ignores AGENTS.md | Tested — value defined only there came back UNKNOWN |
.claude/rules/ loads at launch | Tested |
CLAUDE.local.md and @ imports load | Tested |
Nested CLAUDE.md loads on Read, not at launch | Tested — absent in one run, injected as a system-reminder after a Read in the next |
| Managed settings paths and precedence, import depth of four, skills and subagent ordering, MCP scope ordering | Read only — code.claude.com docs, August 13, 2026 |
| Everything about Codex, Cursor and Gemini CLI | Read only — official docs plus, for Gemini's default context filename, the v0.55.1 source |
One methodological caveat on the tested rows for CLAUDE.md-shaped mechanisms: they show whether
a file reached the model, which is a hard fact. They cannot show which instruction the model would
follow under conflict, because — as the docs say — nothing decides that.
Frequently asked questions
Which CLAUDE.md scope takes the highest precedence?
None of them. Claude Code concatenates every CLAUDE.md it discovers into context rather than letting one override another. The documented order — managed policy, user (~/.claude/CLAUDE.md), project (./CLAUDE.md), local (./CLAUDE.local.md) — is a load order from broadest to most specific, not a precedence ladder. The docs say plainly that if two rules contradict each other, Claude may pick one arbitrarily. If a rule must hold, use a permission deny rule or a PreToolUse hook, not a line of Markdown.
Does .claude/settings.local.json override .claude/settings.json?
Yes. Settings apply highest-first as managed, command line arguments, local (.claude/settings.local.json), project (.claude/settings.json), then user (~/.claude/settings.json). We tested it: a project file setting model to haiku and a local file setting it to sonnet, in the same repo, resolved to sonnet. Permission rules are the exception — they merge across all scopes instead of overriding, and deny is evaluated before allow no matter which file it came from.
Why is my personal skill running instead of the project one?
Because skills resolve in the opposite direction to settings. For skills, enterprise overrides personal and personal overrides project, so a deploy skill in ~/.claude/skills/ shadows the repo's .claude/skills/deploy/ and nothing in the session tells you. Subagents go the other way again — managed, then --agents, then .claude/agents/, then ~/.claude/agents/ — so the project file wins there.
Does Claude Code read AGENTS.md?
No. We put an AGENTS.md next to a CLAUDE.md at a repo root and asked Claude Code 2.1.229 to quote a value defined only in AGENTS.md; it answered UNKNOWN while correctly quoting values from CLAUDE.md, .claude/rules/, CLAUDE.local.md and an @-imported file. The documented fix is a one-line @AGENTS.md import at the top of CLAUDE.md, or symlinking the two. Cursor reads both AGENTS.md and CLAUDE.md automatically; Gemini CLI reads neither unless you set context.fileName.
How do I debug a setting that is being ignored?
In Claude Code, /permissions lists the resolved allow and deny rules with the settings file each came from, /status shows which setting sources are active, /context shows the memory files and subagents actually loaded with their source, and claude doctor prints settings diagnostics without starting a session. Note the gap: no command prints a fully merged settings.json, so key-level attribution outside permissions has to be done by bisecting with --setting-sources.
Can a project config loosen my permissions without asking?
No. permissions.allow and permissions.additionalDirectories in a committed .claude/settings.json only take effect after you accept the workspace trust dialog for that folder. In a folder we never trusted, Claude Code printed "Ignoring 1 permissions.allow entry from .claude/settings.json: this workspace has not been trusted" and ran without it. Deny and ask rules are not gated, because they only restrict.
Where Backgrind fits
Backgrind does not add a layer to any of this. It is a bring-your-own-CLI overlay: it runs the agent you
already run, in an always-on-top window over whatever else is on screen, and pings you when a decision is
waiting. Your CLAUDE.md, your settings.json, your AGENTS.md resolve
exactly as they do in a terminal, because it is your terminal.
The one place Backgrind keeps its own file is approvals: an "Always allow" you click in a notification
writes a granular repo-plus-tool-plus-command rule to ~/.backgrind/policies.json, where deny
beats allow — deliberately the same shape as the rule above, so there is one mental model rather than two.
The agent never reads that file; the daemon consults it before holding a hook. If you run more than one CLI
and are tired of remembering which of them reads which Markdown file, the overlay at least keeps them in one
window. See it in the live demo, or read the
team setup guide for what to commit next.