# Why Does the Settings System Need Five Priority Layers?

Why does a CLI tool need five layers of configuration? Because Claude Code must simultaneously satisfy individual user customization, team project standardization, and enterprise security policy enforcement—and these three often conflict. Only a layered priority system can resolve this elegantly.

> **Source location**: `src/utils/settings/` directory (multiple files), `src/services/settingsSync/` — settings sync service

> 💡 **Plain English**: The settings system is like the layers of clothing you wear—underwear is the default config (comfortable but invisible) → a shirt is project config (team uniform) → a jacket is personal preference (your chosen style) → a bulletproof vest is enterprise policy (security mandates you can't remove). Outer layers cover inner layers, but the bulletproof vest always has the highest priority.

### 🌍 Industry Context

Multi-layer configuration systems are a common engineering practice in developer tools, but the depth of layering and enterprise support varies significantly across AI coding tools:

- **VS Code / Cursor**: The classic three-layer setup—defaults → user global (`settings.json`) → workspace (`.vscode/settings.json`). Supports deep object merging, but has no enterprise policy enforcement layer. Cursor adds AI-related config options on top of this, yet still follows the VS Code three-layer model.
- **Windsurf**: Built on the VS Code architecture, with identical configuration layers and no independent enterprise management policy layer.
- **Aider**: Supports three layers—command-line arguments → environment variables → `.aider.conf.yml` configuration file. No enterprise policy layer or MDM integration.
- **JetBrains IDEs**: Four layers—defaults → IDE global → project → module level, with partial enterprise management via the Toolbox App and JetBrains Gateway.
- **Chrome Browser / Electron Apps**: Support enterprise MDM policies (via Windows Group Policy / macOS configuration profiles), which is the direct inspiration for Claude Code's enterprise policy layer.

Claude Code's five-layer design (especially the four nested sub-layers within enterprise policy: remote API → MDM → file → registry) represents the most complete enterprise-grade solution among AI coding tools. That said, multi-layer configuration itself is a mature software engineering pattern—Git's own config has system → global → local → worktree four layers, and Linux's systemd employs similar drop-in directory override mechanisms.

---

## The Question

How many "settings files" does Claude Code have? From `~/.claude/settings.json` to `.claude/settings.local.json` to enterprise policy, what are the relationships among them? If two files set the same configuration key, which one wins?

---

## It's Actually Five Layers

From lowest to highest priority:

```
1. userSettings      → ~/.claude/settings.json
2. projectSettings   → ./.claude/settings.json
3. localSettings     → ./.claude/settings.local.json
4. flagSettings      → file specified by --settings / SDK inline
5. policySettings    → enterprise management policy (highest)
```

**Higher numbers beat lower numbers**—later-loaded settings override earlier ones. In other words, project settings override your global settings, and enterprise policy overrides everything.

### Purpose of Each Layer

| Source | Controlled By | Committed to git | Purpose |
|--------|---------------|------------------|---------|
| userSettings | You | No | Personal global preferences (model, theme, keybindings) |
| projectSettings | Team | **Yes** | Team-shared configuration (permission rules, MCP servers) |
| localSettings | You | **No (auto-gitignored)** | Local private overrides you don't want to share with colleagues |
| flagSettings | CLI/SDK | N/A | Temporary overrides for automation scenarios |
| policySettings | Admin | N/A | Enterprise policy, cannot be overridden by users |

**Key design**: When writing `localSettings`, Claude Code automatically adds `.claude/settings.local.json` to `.gitignore`. Your private overrides won't accidentally pollute the team settings.

---

## The Four Nested Layers of Enterprise Policy

Enterprise-grade `policySettings` itself has four sub-sources (from highest to lowest priority):

```
remote  → remote management API (cloud-delivered, highest)
MDM     → system-level management (Windows HKLM registry / macOS Property List)
file    → managed-settings.json + managed-settings.d/*.json
HKCU    → Windows user registry (user-writable, lowest)
```

It uses **first-source-wins**: as long as the remote source has a setting, MDM doesn't take effect; as long as MDM has a setting, the file source doesn't take effect.

The `managed-settings.d/` directory draws inspiration from Linux drop-in conventions:
```
managed-settings.json    ← base policy
managed-settings.d/
  10-security.json       ← security team's policy
  20-developer-tools.json← developer tools team's policy
  99-overrides.json      ← final overrides
```

Files are merged in alphabetical order, allowing each team to maintain its own policy fragments independently without interfering with one another.

---

## Merge Semantics: Deep Merge for Objects, Full Replacement for Arrays

> 📚 **Course Connection**: Configuration merge strategies are a core topic in the "Configuration Management" chapter of any *Software Engineering* curriculum. The choice of "deep object merge + array replacement" maps to two classic merge semantics—**recursive merge** (similar to Git's three-way merge) and **override replacement**. In *Distributed Systems*, this also connects to conflict resolution strategies for state replication: CRDTs use merge semantics (e.g., G-Counter), while LWW-Register (Last-Writer-Wins) uses override semantics—Claude Code's array replacement is essentially an LWW policy.

Settings are merged using lodash `mergeWith` with the following rules:

**Object fields**: deep merge (keys in nested objects override individually)
```jsonc
// userSettings
{"model": {"main": "claude-opus"}}
// projectSettings
{"model": {"temperature": 0.7}}
// merged result
{"model": {"main": "claude-opus", "temperature": 0.7}}
```

**Array fields**: full replacement (not appended, but overwritten)
```jsonc
// userSettings
{"allowedTools": ["Read", "Write"]}
// projectSettings
{"allowedTools": ["Read", "Grep"]}
// merged result
{"allowedTools": ["Read", "Grep"]}  ← full replacement
```

**Important implication**: If enterprise policy sets any array-typed field, it will **fully override** the corresponding field from all lower-priority sources. Admins can use arrays to precisely control the allowed tools list, and users cannot "sneak in" extra tools.

---

## Cowork Mode

If you launch with the `--cowork` flag, `userSettings` reads from `cowork_settings.json` instead of `settings.json`:

```
Normal mode: ~/.claude/settings.json
--cowork mode: ~/.claude/cowork_settings.json
```

This lets you maintain two independent global configurations—personal projects use `settings.json`, work projects use `cowork_settings.json`, and you switch with a single flag.

---

## What We Can Learn from This

**The key decisions in layered configuration are "merge or override" and "who has the highest authority."**

Claude Code's choices:
- Objects: deep merge (allows each layer to override only what it cares about)
- Arrays: replacement (allows admins to precisely control list-type configuration)
- Highest authority: enterprise policy (remote > MDM > file > user registry)

This design makes all three scenarios work—individual developers, team collaboration, and enterprise compliance. You set your preferences in `~/.claude/settings.json`, the team sets standards in `.claude/settings.json`, and enterprise IT enforces compliance in `policySettings`. The three never conflict because lower layers can never bypass upper layers.

---

## Code Landmarks

- `src/utils/settings/constants.ts`, line 7: `SETTING_SOURCES` array, priority order
- `src/utils/settings/settings.ts`, line 799: `getInitialSettings()` function comment (merge explanation)
- `src/utils/settings/settings.ts`, line 319: `getSettingsForSourceUncached()` — policySettings four-layer logic
- `src/utils/settings/settings.ts`, line 65: `loadManagedFileSettings()` — drop-in directory implementation

---

## Design Trade-offs and Costs

However, five layers of configuration also introduce complexity. When a user finds that a setting "doesn't work," the problem could lie at any layer—troubleshooting requires checking layer by layer, which is a significant cognitive burden for average users. The "replace" rather than "merge" semantics for arrays, while enabling precise admin control, also means that allowed rules a user added in `userSettings` might be completely overridden by a project-level array. If a bug occurs in the merge logic of the configuration hierarchy, the impact scope could be global—because every feature depends on this settings system. The risk of such "unified infrastructure" is that the cost of a single point of failure is extremely high.

---

*Quality check:*
- [x] Coverage: five sources, enterprise policy four layers, merge semantics, cowork mode all covered
- [x] Fidelity: file paths and priority order from constants.ts SETTING_SOURCES
- [x] Readability: tables and JSON examples build intuition
- [x] Critical: pointed out the important implication of array replacement semantics
