17 / 20Configuration6 MIN READ

settings.json

settings.json is the JSON configuration file Claude Code reads from four layered scopes — managed, user, project, and local — to decide model, permissions, hooks, environment variables, and everything else about how a session behaves.

Configuration toggles resolving across four scopesFour stacked scope rows — managed, local, project, and user — each with a toggle. Higher scopes win, so the managed row stays locked on and the user row is overridden.SCOPEPRECEDENCEMANAGEDmanaged-settings.jsonLOCAL.claude/settings.local.jsonPROJECT.claude/settings.jsonUSER~/.claude/settings.jsonvalues override downward · permission rules merge, deny always wins

What it is

Four files, in priority order. Managed settings are deployed by IT through server-managed settings, an MDM policy, or managed-settings.json in a system directory, and cannot be overridden. User settings at ~/.claude/settings.json apply across all your projects. Project settings at .claude/settings.json are committed and shared with the repository. Local settings at .claude/settings.local.json are yours alone. Command-line arguments sit between managed and local.

Most keys override: when the same key appears in two scopes, the higher-priority one wins. Permission rules are the exception — they merge, and a deny at any scope beats an allow at any other. Arrays such as claudeMdExcludes merge across layers too.

As of v2.1.211 Claude Code reads and writes .claude/settings.local.json at the root of the git repository, resolved through worktrees to the main checkout, so one file covers sessions started in any subdirectory or worktree. It stays in the starting directory in three cases: outside a git repository, when the repository root is your home directory, and in Agent SDK sessions. Claude Code adds the file to your global git excludes when it saves a setting there.

What it does for you

  • It puts a team’s Claude Code setup in version control. Permission rules, hooks, and the model are reviewed in a pull request like any other configuration.
  • It separates shared from personal cleanly. Project settings ship; local settings hold your own approvals and experiments without polluting the repository.
  • It gives an organisation an enforcement layer. Managed settings cannot be overridden even by command-line flags, so a security policy holds on every machine.

How it works

  1. 01Claude Code loads every scope at startup

    Managed, user, project, and local, plus anything passed with --settings. Run /status and read the Setting sources line to see which files loaded.

  2. 02Values resolve by priority, rules by merge

    Managed beats CLI arguments, which beat local, which beats project, which beats user. Permission rules merge across all of them, with deny evaluated before ask before allow.

  3. 03Most keys reload without a restart

    Claude Code watches the files and applies changes to the running session, including permissions, hooks, and apiKeyHelper. The ConfigChange hook fires for each detected change, with a matcher naming which source changed — user_settings, project_settings, local_settings, policy_settings, or skills — and a hook that exits 2 blocks the change from taking effect, except for policy settings.

  4. 04Two keys wait for a restart

    model is read once at session start — use /model to switch mid-session. outputStyle is part of the system prompt, which is rebuilt on /clear or restart.

  5. 05Project allow rules wait for workspace trust

    permissions.allow and permissions.additionalDirectories in a project file grant capability, so Claude Code applies them only after you accept the workspace trust dialog. Deny and ask rules are unaffected.

  6. 06Managed settings parse tolerantly, others do not

    An invalid entry in managed settings is stripped with a warning and the rest of the policy is enforced, so one typo cannot disable an organisation’s whole policy. Security-enforcement fields fail closed instead: an invalid allowedMcpServers is treated as an empty allowlist, and an invalid allowManagedMcpServersOnly is treated as true. A user, project, or local file that fails validation is rejected as a whole and reported to you at startup.

How to implement it

  1. 01Add the schema line first

    Put "$schema": "https://json.schemastore.org/claude-code-settings.json" at the top so your editor autocompletes keys and flags typos.

  2. 02Decide what is shared and what is personal

    Permission rules, hooks, and required environment variables go in .claude/settings.json and get committed. Your model preference and your own approvals go in ~/.claude/settings.json or .claude/settings.local.json.

  3. 03Commit the project file and gitignore the local one

    Claude Code adds **/.claude/settings.local.json to your global git excludes when it writes there. If you create the file by hand, add it to .gitignore yourself.

  4. 04Verify the file actually loaded

    Run /status and check Setting sources. A file with broken JSON does not appear at all, which is the fastest way to spot a syntax error.

  5. 05Use /doctor when something is not taking effect

    It reports unparseable settings files, duplicate installs, slow hooks, and unused skills or MCP servers against their context cost.

Examples

.claude/settings.jsonjson
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",

  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(git diff *)",
      "Bash(git status *)"
    ],
    "ask": ["Bash(git push *)"],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Edit(./db/migrate/**)"
    ],
    "additionalDirectories": ["../shared-types"]
  },

  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh",
            "statusMessage": "Formatting",
            "timeout": 60
          }
        ]
      }
    ]
  },

  "env": {
    "NODE_ENV": "development",
    "BASH_DEFAULT_TIMEOUT_MS": "300000"
  },

  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["registry.npmjs.org", "github.com", "*.github.com"]
    }
  },

  "enabledMcpjsonServers": ["github", "postgres"],
  "autoCompactWindow": 500000,
  "cleanupPeriodDays": 90,
  "enabledPlugins": ["mcp-server-dev@claude-plugins-official"]
}
A committed project file. JSON has no comments, so the commentary lives in the caption and in CLAUDE.md rather than in the file.
~/.claude/settings.jsonjson
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "model": "opus",
  "permissions": {
    "deny": [
      "Read(//**/.env)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)"
    ]
  },
  "statusLine": {
    "type": "command",
    "command": "jq -r '.workspace.current_dir + \" · \" + .model.display_name'"
  },
  "cleanupPeriodDays": 60
}
Personal settings, applied in every project. Note the `//` prefix: a single leading slash would anchor at `~/.claude`, not the filesystem root.
Checking what loadedbash
/status     # the "Setting sources" line lists every file that loaded.
            # A file with broken JSON does not appear at all.

/doctor     # unparseable settings files, duplicate installs, slow hooks,
            # unused skills and MCP servers against their context cost.

/config     # the interactive settings UI
/config thinking=false     # set one key without opening the UI
Two commands answer almost every "why is my setting being ignored" question.

Use it when

  • Sharing permission rules and hooks with a team through a committed .claude/settings.json.
  • Setting a personal default model and a blanket credential deny list that follows you into every project.
  • Enforcing sandboxing and MCP server allowlists organisation-wide through managed settings.
  • Turning on project-scoped MCP servers for everyone with enabledMcpjsonServers.
  • Setting autoCompactWindow so compaction fires at a size that suits how your team works.

Avoid it when

  • You need a comment. JSON has none, so a settings file cannot explain itself — put the rationale in CLAUDE.md or the pull request instead.
  • The value is a secret. Settings files are read as plain text and the project one is committed; use env to reference a variable, or an apiKeyHelper.
  • You want a repository to grant itself capability. Project allow rules wait for workspace trust, and defaultMode: "auto" is ignored from project and local files by design.
  • The behaviour is conditional. Settings are static values; a hook can inspect the actual tool call and decide.

Common mistakes

  • SYMPTOMA settings file is edited and nothing changes.

    FIXRun /status and read Setting sources. A file with a JSON syntax error does not appear at all — user, project, and local files are rejected as a whole when validation fails.

  • SYMPTOMChanging model in settings mid-session has no effect.

    FIXmodel is read once at session start. Use /model to switch mid-session; the settings value applies to new sessions.

  • SYMPTOMA path rule in ~/.claude/settings.json does not match the project directory it was written for.

    FIXA /path pattern anchors at the settings source, which for user settings is ~/.claude. Use // for absolute paths or ~/ for home-relative ones.

  • SYMPTOMAllow rules in a cloned repository’s .claude/settings.json do nothing.

    FIXThey grant capability, so they apply only after you accept the workspace trust dialog for that folder. Deny and ask rules apply immediately.

  • SYMPTOM.claude/settings.local.json gets committed by accident.

    FIXClaude Code adds it to your global git excludes only when it writes a setting there itself. A file you create by hand needs a .gitignore entry from you.

Best practices

  • Include the $schema line so your editor validates keys as you type them.
  • Commit .claude/settings.json and keep .claude/settings.local.json out of git.
  • Put credential deny rules in user settings with // or ~/ anchors so they apply in every project.
  • Run /status after every edit; the Setting sources line is the fastest syntax check you have.
  • Keep secrets out of settings files — reference environment variables instead.
  • Use managed settings for anything that must not be overridable, since no other scope, including CLI flags, can beat them.

Try it in five minutes

Watch settings precedence and validation behave.

  1. 1.Add {"$schema":"https://json.schemastore.org/claude-code-settings.json","permissions":{"allow":["Bash(echo *)"]}} to .claude/settings.json in a test repository.
  2. 2.Start claude, run /status, and confirm the file appears under Setting sources. Run /permissions and find the rule with its source.
  3. 3.Add {"permissions":{"deny":["Bash(echo *)"]}} to .claude/settings.local.json and ask Claude to run echo hi. It is refused: deny beats allow across scopes.
  4. 4.Delete a closing brace from .claude/settings.json and restart. Run /status — the file is gone from the list, because an invalid file is rejected whole.
  5. 5.Fix the JSON, then run /doctor and read what it reports about your configuration.

Verified against code.claude.com/docs/en/settings on 2026-08-09. See content/SOURCES.md for the full table.

← / → MOVE BETWEEN CONCEPTS