← All posts

Explainer

Which config file actually wins?

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

MechanismResolution (winner first)Merge or replace?
settings.jsonmanaged → CLI args → local → project → userScalars replace, arrays concatenate
Permission rulesdeny → ask → allow, from any scopeMerge across every scope
CLAUDE.mdno winner — all files load togetherConcatenate
.claude/rules/project rules over user rulesConcatenate, project last
Skills and commandsenterprise → personal → projectReplace by name
Subagentsmanaged → --agents → project → user → pluginReplace by name
MCP serverslocal → project → userReplace whole entry by name
With BackgrindBackgrind 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.

same two scopes, four different outcomes ~/.claude  (personal) .claude/  (committed) settings.json user project repo wins skills/ personal project laptop wins rules/  agents/ user project repo wins CLAUDE.md user + project both load Permission rules ignore the axis entirely: they merge from every scope, and deny beats allow.
Skills point the opposite way to the 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:

RankScopeWhere
1Managed / policymacOS /Library/Application Support/ClaudeCode/managed-settings.json; Linux and WSL /etc/claude-code/; Windows C:\Program Files\ClaudeCode\ and HKLM\SOFTWARE\Policies\ClaudeCode
2Command line arguments--settings, --model, --allowedTools, …
3Local.claude/settings.local.json — at the git repository root since v2.1.211
4Project.claude/settings.json
5User~/.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 shapeAcross scopesConfidence
Scalars (model, theme)Higher scope replacesDocumented + tested
Arrays (permissions.*, sandbox.filesystem.allowWrite)Concatenated and de-duplicated, not replaced — a lower scope can add entriesDocumented
hooksMerge across levels; identical handlers defined twice run onceDocumented
fallbackModelException: the highest scope that defines it supplies the whole ordered chainDocumented
availableModelsException: a managed list applies as-is and cannot be extendedDocumented
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:

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:

OrderScopeLocation
1stManaged policymacOS /Library/Application Support/ClaudeCode/CLAUDE.md; Linux and WSL /etc/claude-code/CLAUDE.md; Windows C:\Program Files\ClaudeCode\CLAUDE.md
2ndUser~/.claude/CLAUDE.md
3rdProject./CLAUDE.md or ./.claude/CLAUDE.md
4thLocal./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:

PrioritySubagent sourceScope
1 (highest)Managed settingsOrganization-wide
2--agents flagCurrent 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.

ScopeStored inShared with the team
Local (wins)~/.claude.json, scoped to the current projectNo
Project.mcp.json at the repository root, not inside .claude/Yes, via version control
User~/.claude.jsonNo

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:

ToolInstruction files readConflict 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:

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

SituationOutcome
Two CLAUDE.md files contradict each otherUndefined. The docs say Claude "may pick one arbitrarily"
A rule matches both deny and allowDeny. Always, from any scope, regardless of specificity
A skill and a slash command share a nameThe skill wins
Two subagent files in one .claude/agents/ tree share a nameUndefined — filesystem read order. /doctor flags it
Two Cursor rules share a filename in different foldersBoth 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 subdirectoryRepository 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.

CommandAnswers
/statusWhich setting sources are active at all, including whether managed settings are in effect
/permissionsThe resolved allow, ask and deny rules and the settings file each one came from. The only fully attributed view
/contextWhat is actually occupying the window: memory files, skills, MCP tools, and subagents with the source each loaded from
/memoryThe memory file locations across user and project scopes
/doctor, claude doctorInvalid settings files, duplicate subagent names, unused extensions. The CLI form prints diagnostics without starting a session
claude --setting-sources user,projectBisect by hand: drop a layer and see whether the behaviour changes
claude --safe-modeAll customizations off — CLAUDE.md, skills, plugins, hooks, MCP, commands. Managed policy still applies
CLAUDE_CONFIG_DIR=/tmp/clean claudeA clean-room config directory, for when you suspect your own home directory
InstructionsLoaded hookLogs 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.

ClaimEvidence
local > project > user for settings.jsonTested — three conflicting model values, resolved model read from --output-format json
deny beats allow across scopesTested — project deny vs local allow, tool call denied
Project permissions.allow is trust-gatedTested — stderr warning, quoted above
Claude Code ignores AGENTS.mdTested — value defined only there came back UNKNOWN
.claude/rules/ loads at launchTested
CLAUDE.local.md and @ imports loadTested
Nested CLAUDE.md loads on Read, not at launchTested — 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 orderingRead only — code.claude.com docs, August 13, 2026
Everything about Codex, Cursor and Gemini CLIRead 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.