The same parameter can come from several sources, and all fine-tuning rests on knowing their precedence. For an ordinary scalar value it is this: managed (the organization's policy) overrides CLI arguments, those override local (personal to this repository), then project (team-shared in the repository) and user (a personal default for all projects). A higher layer overrides the lower ones - this is precedence, and without understanding it, configuration turns into guessing why a value is "wrong".
Layers differ not only in priority but in whether they are committed. managed comes from the administrator and carries mandatory security and compliance rules. user lives in ~/.claude and is not committed - these are personal defaults and UI. project lies in .claude/settings.json and .mcp.json, is committed and sets the team's single workflow. local in settings.local.json is not committed - for machine paths and experiments. It helps to gather this into a table once, to put a setting in the right layer.
| Scope | Where | Commit | For |
|---|---|---|---|
| managed | server / OS policy / managed-settings.json | admin | Mandatory security/compliance |
| user | ~/.claude/settings.json | no | Personal defaults, UI |
| project | .claude/settings.json, .mcp.json | yes | The team's single workflow |
| local | .claude/settings.local.json | no | Machine paths, experiments |
The merging of layers works by value type. A scalar value from a higher source simply replaces the lower one. Objects usually deep-merge, and documented arrays may merge and de-duplicate. But there is a principled exception - permission allow, ask and deny: rules from different scopes are kept together rather than replaced. This is done deliberately, so a broad user allow does not erase a project or managed deny; security here matters more than the convenience of overriding.
It helps to see a minimal settings with $schema once, so the editor gives autocomplete and validation. Below is a fragment with a model and permissions. A caveat: the schema may update with a delay relative to the freshest CLI, so on a discrepancy you first check the official settings page and changelog, and then run /doctor. The schema is an editor helper, not the final truth about available keys.
Managed settings are delivered by several paths, and their location depends on the OS. They may come remotely after sign-in, via MDM, the registry or a plist, or from a system directory - its own on macOS, Linux/WSL and Windows. In the system directory you can use managed-settings.d with drop-in files: the base loads first, drop-ins are sorted by name, later scalars win, arrays merge, objects deep-merge. Numeric prefixes like 10-telemetry.json make the order visible.
Worth understanding separately is that ~/.claude.json is not a settings file. In it Claude Code stores the OAuth session, user and local MCP state, project trust and allowed tools, caches. It is not edited as team configuration and not committed. Confusing this file with settings is a frequent mistake: edits in it do not set policy but can damage the session and trust state, which then has to be restored by hand.
The main rule of working with layers is to verify the source rather than assume. /status shows the Setting sources line: a broken JSON source is not counted as loaded at all, and this is easy to miss. /doctor diagnoses invalid or stripped entries and conflicting definitions. Claude Code keeps up to five timestamped backups of configuration, but this does not replace version control for project policy - the real history of changes lives in git.
Officially most settings are re-read live, but there are exceptions to know: model is read at start (change via /model), and outputStyle goes into the system prompt and requires /clear or a restart. The typical scope failures are putting a mandatory deny in a user file, where it will be removed; confusing ~/.claude.json with settings; assuming the effective value instead of checking /status. Put a setting in the right layer and always check against the actual source.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"model": "sonnet",
"permissions": {
"allow": ["Bash(npm run test:*)"],
"deny": ["Read(./.env)"]
}
}
// precedence: managed > CLI > local > project > user; a deny from scopes stays