Configuration System
Claude Code’s configuration system is a multi-layered architecture where settings flow from 5+ sources with well-defined precedence. Understanding this system is crucial because configuration affects everything: available tools, permission rules, model selection, and behavior.
Configuration Sources Overview
Section titled “Configuration Sources Overview”Settings are loaded from multiple sources, with later sources overriding earlier ones:
graph TB subgraph "Lowest Priority" A["1. User Settings<br/>~/.claude/settings.json"] end
subgraph "Medium Priority" B["2. Project Settings<br/>.claude/settings.json"] C["3. Local Settings<br/>.claude/settings.local.json"] end
subgraph "High Priority" D["4. Flag Settings<br/>--settings CLI flag"] end
subgraph "Highest Priority" E["5. Policy Settings<br/>managed-settings.json / remote API"] end
A --> B --> C --> D --> E
style E fill:#f96,stroke:#333,stroke-width:2pxSource Definitions
Section titled “Source Definitions”From src/utils/settings/constants.ts:
export const SETTING_SOURCES = [ 'userSettings', // Global: ~/.claude/settings.json 'projectSettings', // Shared: .claude/settings.json (checked into VCS) 'localSettings', // Private: .claude/settings.local.json (gitignored) 'flagSettings', // CLI: --settings flag 'policySettings', // Enterprise: managed-settings.json or remote API] as const| Source | Location | VCS | Use Case |
|---|---|---|---|
| userSettings | ~/.claude/settings.json | N/A (home dir) | Personal global preferences |
| projectSettings | .claude/settings.json | Committed | Team-shared project rules |
| localSettings | .claude/settings.local.json | Gitignored | Private project overrides |
| flagSettings | --settings CLI arg | N/A | CI/CD, scripting |
| policySettings | /etc/claude-code/managed-settings.json | N/A | Enterprise IT management |
Precedence Rules
Section titled “Precedence Rules”The override logic uses lodash’s mergeWith with a custom merge strategy:
function settingsMergeCustomizer(objValue: unknown, srcValue: unknown): unknown { // Arrays are replaced, not concatenated if (Array.isArray(srcValue)) { return srcValue } // Objects are deep-merged return undefined // fall through to default deep merge}Key behavior: Arrays are replaced (not merged) by higher-priority sources. This means a project can completely override user-level permission rules.
Policy Settings (Enterprise)
Section titled “Policy Settings (Enterprise)”Policy settings have the highest priority and support two mechanisms:
1. File-based Managed Settings
Section titled “1. File-based Managed Settings”export function loadManagedFileSettings(): { settings: SettingsJson | null errors: ValidationError[]} { // Base: /etc/claude-code/managed-settings.json (or platform equivalent) const { settings } = parseSettingsFile(getManagedSettingsFilePath())
// Drop-ins: /etc/claude-code/managed-settings.d/*.json // Sorted alphabetically, higher precedence const entries = readdirSync(dropInDir) .filter(d => d.name.endsWith('.json') && !d.name.startsWith('.')) .sort() // Merge each on top}The drop-in pattern mirrors systemd conventions — teams can ship independent policy fragments (e.g., 10-otel.json, 20-security.json) without editing a single file.
2. Remote Managed Settings
Section titled “2. Remote Managed Settings”For organizations using Anthropic’s management API, settings are fetched remotely and cached:
export function initializeRemoteManagedSettingsLoadingPromise(): voidexport function waitForRemoteManagedSettingsToLoad(): Promise<void>MDM (Mobile Device Management)
Section titled “MDM (Mobile Device Management)”On macOS and Windows, Claude Code also reads from platform-native MDM:
export function startMdmRawRead(): void { // macOS: plutil to read com.anthropic.claude-code preferences // Windows: registry query for HKCU\Software\Anthropic\Claude Code}MDM reads are fired at import time (before init) to run in parallel with module loading.
Zod Schema Validation
Section titled “Zod Schema Validation”All settings are validated against a Zod schema:
import { z } from 'zod/v4'
export const SettingsSchema = z.object({ // Permission rules permissions: z.object({ allow: z.array(PermissionRuleSchema).optional(), deny: z.array(PermissionRuleSchema).optional(), ask: z.array(PermissionRuleSchema).optional(), }).optional(),
// Environment variables env: z.record(z.string()).optional(),
// MCP server configuration mcpServers: z.record(McpServerConfigSchema).optional(),
// Hooks hooks: z.record(HookConfigSchema).optional(),
// Model preferences model: z.string().optional(),
// ... many more fields}).passthrough()
export type SettingsJson = z.infer<typeof SettingsSchema>Invalid settings are reported but don’t crash the application:
export type ValidationError = { source: SettingSource field: string message: string}
// Settings loading always returns both valid settings and errorsexport type SettingsWithErrors = { settings: SettingsJson errors: ValidationError[]}CLAUDE.md Memory System
Section titled “CLAUDE.md Memory System”Beyond JSON settings, Claude Code loads markdown instruction files — the CLAUDE.md memory system:
// src/utils/claudemd.ts — File loading hierarchy (from comments)// 1. Managed memory → /etc/claude-code/CLAUDE.md// 2. User memory → ~/.claude/CLAUDE.md// 3. Project memory → CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md// 4. Local memory → CLAUDE.local.mdLoading Order
Section titled “Loading Order”Files are loaded in reverse priority order — the last-loaded file has the highest priority:
graph TB A["1. Managed Memory<br/>/etc/claude-code/CLAUDE.md<br/>(Lowest priority)"] B["2. User Memory<br/>~/.claude/CLAUDE.md"] C["3. Project Memory - Git Root<br/>CLAUDE.md in repo root"] D["4. Project Memory - Current Dir<br/>CLAUDE.md in cwd"] E["5. Local Memory<br/>CLAUDE.local.md<br/>(Highest priority)"]
A --> B --> C --> D --> E
style E fill:#9f6,stroke:#333,stroke-width:2pxProject Memory Discovery
Section titled “Project Memory Discovery”Project memory files are discovered by traversing from the current directory up to the repository root:
// src/utils/claudemd.ts (conceptual)// For each directory from cwd up to git root:// 1. Check CLAUDE.md// 2. Check .claude/CLAUDE.md// 3. Check .claude/rules/*.md (all .md files)// Files closer to cwd have higher priority@include Directive
Section titled “@include Directive”Memory files support including other files:
<!-- In CLAUDE.md -->@./coding-standards.md@./api-conventions.md@~/personal-preferences.mdconst MEMORY_INSTRUCTION_PROMPT = 'Codebase and user instructions are shown below. Be sure to adhere to these instructions. ' + 'IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.'
// Recommended max character count for a memory fileexport const MAX_MEMORY_CHARACTER_COUNT = 40000Rules for @include:
@pathor@./path— Relative to the including file@~/path— Relative to home directory@/path— Absolute path- Only works in leaf text nodes (not inside code blocks)
- Circular references are prevented
- Non-existent files are silently ignored
- Only text file extensions are allowed (
.md,.txt,.ts,.py, etc.)
Configuration Caching
Section titled “Configuration Caching”Settings are aggressively cached to avoid repeated filesystem reads:
export function getCachedSettingsForSource(source: SettingSource): SettingsJson | nullexport function setCachedSettingsForSource(source: SettingSource, settings: SettingsJson): voidexport function resetSettingsCache(): voidCache invalidation happens:
- On
resetSettingsCache()calls (after worktree creation, plugin reload) - On settings file changes detected by the settings change detector
- On remote settings refresh
export function settingsChangeDetector(): void { // Watches settings files for changes // Triggers cache invalidation and hook re-capture}Environment Variable Integration
Section titled “Environment Variable Integration”Settings can set environment variables:
{ "env": { "ANTHROPIC_MODEL": "claude-sonnet-4-20250514", "NODE_OPTIONS": "--max-old-space-size=4096" }}Environment variables are applied in two phases:
- Safe phase (before trust dialog) — Only non-sensitive variables:
export function applySafeConfigEnvironmentVariables(): void// Applied during init(), before any trust dialog- Full phase (after trust) — All variables including sensitive ones:
export function applyConfigEnvironmentVariables(): void// Applied after trust is establishedSetting Source Control
Section titled “Setting Source Control”The --setting-sources CLI flag allows restricting which sources are loaded:
export function parseSettingSourcesFlag(flag: string): SettingSource[] { // "user,project" → ['userSettings', 'projectSettings'] // Valid names: user, project, local}
export function getEnabledSettingSources(): SettingSource[] { const allowed = getAllowedSettingSources() // Always include policy and flag settings (cannot be disabled) const result = new Set<SettingSource>(allowed) result.add('policySettings') result.add('flagSettings') return Array.from(result)}Critical rule: policySettings and flagSettings are always included — they cannot be disabled by any flag. This ensures enterprise policy and explicit CLI flags always take effect.
Configuration in Practice
Section titled “Configuration in Practice”How Settings Reach Tools
Section titled “How Settings Reach Tools”graph LR S["settings.ts"] --> P["permissionSetup.ts"] P --> TPC["ToolPermissionContext"] TPC --> T["tools.ts → getTools()"] T --> POOL["Tool pool for session"]
S --> E["managedEnv.ts"] E --> ENV["process.env"] ENV --> TOOLS["Tool implementations"]How CLAUDE.md Reaches the Model
Section titled “How CLAUDE.md Reaches the Model”graph LR CM["claudemd.ts"] --> QC["queryContext.ts"] QC --> SP["System prompt assembly"] SP --> C["claude.ts"] C --> API["Anthropic API"]