Claude Code settings.json Not Loading After Edit

Edited ~/.claude/settings.json but Claude Code ignores your hook, permission, or env? Run /doctor first. Usually JSON syntax, wrong scope, or precedence shadowing.

You edited ~/.claude/settings.json or your project .claude/settings.json to add a hook, set a permission, or define an environment variable, and Claude Code acts like the change never happened: the hook never fires, the permission still prompts, the env var is empty.

Fastest fix: run /doctor inside Claude Code. As of June 2026 it tells you, in one screen, which settings files loaded, their source, and any invalid entry with the exact field name. If /doctor flags nothing, run jq . ~/.claude/settings.json in a shell to catch a syntax error that silently invalidates the whole file. Those two checks resolve the large majority of “my settings won’t load” cases.

Claude Code reads settings from a layered set of files (user, project, project-local, plus enterprise-managed) and merges them by precedence. A JSON syntax error, a typo in a key name, an edit in the wrong scope, or a higher layer shadowing yours can all make your change invisible. Once you know which scope should win and which one Claude Code is actually reading, the fix is short.

One thing that changed and trips up older guides: you usually do not need to restart. As of current versions (June 2026), edits to permissions, hooks, and apiKeyHelper hot-reload into the running session — Claude Code watches the files and fires a ConfigChange hook on each detected change. Only model and outputStyle still require a restart (or /model and /clear respectively).

Which bucket are you in

SymptomMost likely causeJump to
/doctor reports an invalid entry or parse errorJSON syntax error or unknown keyStep 1
Change works for a teammate but not you (or vice versa)Edited the wrong scope / precedence shadowingStep 2
/hooks shows “No hooks configured” or 0 matchersWrong file, wrong event/matcher shape, or syntax errorStep 3
Permission still prompts despite an allow ruleA deny or ask rule wins; rules merge across scopesStep 4
env var is empty inside a Bash tool callWrong scope, or a non-string valueStep 6
Nothing loads; file looks fineFile owned by root / unreadableStep 5

Common causes

Ordered by hit rate, highest first.

1. JSON syntax error invalidates the whole file

A trailing comma, an unquoted key, a // comment, or a smart quote makes the entire settings file fail to parse, and Claude Code falls back to defaults. /doctor now surfaces this, but jq gives you the line.

How to judge: run jq . ~/.claude/settings.json. If it errors, the file is invalid. JSON has no comments — a stray // or # is a common culprit.

2. You edited the wrong scope

There are three file-based scopes plus managed settings: ~/.claude/settings.json (user), <project>/.claude/settings.json (project, committed), and <project>/.claude/settings.local.json (local, gitignored). A user-level scalar value is overridden by a project-level one with the same key.

How to judge: run ls -la ~/.claude/settings.json "$PWD"/.claude/settings*.json and note which exist. Then run /doctor to see the resolved source for each setting.

3. Settings key was renamed or never existed

Claude Code occasionally renames keys, and a typo’d key is silently ignored. Add the JSON schema for autocomplete and validation:

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

How to judge: with $schema set, your editor flags unknown keys. /doctor also reports invalid entries in managed settings with the offending field name.

4. Permission rules merge and evaluate deny -> ask -> allow

Unlike most settings, the array permissions.allow, permissions.ask, and permissions.deny lists do not override across scopes — they are concatenated and de-duplicated, so a rule in user settings stacks with one in project settings. Rules then evaluate in deny -> ask -> allow order (as of June 2026), so a leftover deny or ask rule anywhere in the stack beats your new allow.

How to judge: search every scope for the tool you are gating, for example grep -r '"Bash' ~/.claude/settings.json .claude/settings*.json. A deny or ask match elsewhere is winning.

5. File permissions block Claude Code from reading the file

If you edited as root or via a script, the file may now be 600 root-owned, and the user-launched Claude Code process cannot read it.

How to judge: stat -f '%Su %Lp' ~/.claude/settings.json (macOS) or stat -c '%U %a' ~/.claude/settings.json (Linux). If the owner is not you, or perms are too tight, fix it.

6. Environment variable under the wrong scope or wrong type

The env key is a flat object of string-to-string pairs applied to every session and every subprocess Claude Code spawns. Numbers and booleans must still be written as quoted strings; a bare true or 5 is invalid JSON and kills the file.

How to judge: inside a Bash tool call, run printenv MY_VAR. Empty means it was not loaded — re-check scope and that the value is a quoted string.

Before you start

  • Back up the file. Claude Code keeps timestamped backups (the five most recent) automatically, but a manual copy is safer.
  • Decide which scope you actually want: user-global, project-shared, or project-local.
  • Make sure jq is installed; you will use it to validate.

Information to collect

  • Claude Code version: claude --version.
  • Who is running Claude Code: whoami.
  • ls -la ~/.claude/ and ls -la .claude/ from your project.
  • The full contents of every involved settings file.
  • jq . ~/.claude/settings.json (note any error).
  • /doctor output, and /hooks if the problem is a hook.

Step-by-step fix

Step 1: Run /doctor, then validate JSON

Inside Claude Code, run /doctor. It reports which settings files loaded, their source, and any invalid entry with the field name. Then validate each file in a shell:

jq . ~/.claude/settings.json
jq . .claude/settings.json 2>/dev/null
jq . .claude/settings.local.json 2>/dev/null

A parse error halts everything else — fix it first. The usual offenders: a trailing comma after the last item in an object or array, a // or # comment, or a smart quote pasted from a doc.

Step 2: Confirm which scope is winning

Precedence, highest first (as of June 2026):

  • Managed / enterprise settings (cannot be overridden)
  • Command-line arguments (for that session only, e.g. claude --model opus)
  • .claude/settings.local.json (project, local)
  • .claude/settings.json (project, shared)
  • ~/.claude/settings.json (user)

For scalar values, the highest layer that defines the key wins. Move your edit to the right layer, or remove the lower one. If managed settings exist, they win over everything, including CLI flags. The managed file lives at:

  • macOS: /Library/Application Support/ClaudeCode/managed-settings.json
  • Linux / WSL: /etc/claude-code/managed-settings.json
  • Windows: C:\Program Files\ClaudeCode\managed-settings.json

Note: the old Windows path C:\ProgramData\ClaudeCode\managed-settings.json was deprecated in v2.1.75.

Step 3: For hooks, use /hooks to confirm load

Run /hooks inside the session. It lists every configured hook, its source (user / project / managed), and the matcher and handler details. If it shows “No hooks configured” or 0 matchers despite your file, the hook is in a file Claude Code is not reading, or the event/matcher shape is wrong. Hooks nest three levels: the event (PreToolUse, PostToolUse, Stop, and so on), a matcher group that filters by tool name, and a handler that names the command, HTTP endpoint, or MCP tool.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "echo blocked >&2; exit 2" }
        ]
      }
    ]
  }
}

Step 4: For permissions, search every scope

Because permissions.allow, permissions.ask, and permissions.deny merge across scopes and evaluate deny -> ask -> allow, a stray rule in another file can override your intent:

grep -rn 'permissions' ~/.claude/settings.json .claude/settings*.json

Remove or fix the higher-priority deny/ask rule, or tighten the pattern so it no longer matches.

Step 5: Verify file ownership and perms

ls -la ~/.claude/settings.json
chmod 600 ~/.claude/settings.json
chown "$USER" ~/.claude/settings.json

The file must be readable by the user running Claude Code.

Step 6: For env, double-check the type

{
  "env": {
    "MY_API_BASE": "https://api.example.com",
    "DEBUG": "true"
  }
}

Use strings even for booleans and numbers, then convert in your script. The env block applies to every session and every subprocess.

Step 7: Reload — and only restart if needed

For permissions, hooks, and apiKeyHelper, just save the file; the running session hot-reloads it (you can watch with the ConfigChange hook). Only model and outputStyle need action: use /model to switch model mid-session, and /clear or a restart to apply a new outputStyle. If you truly want a clean reload, type /exit and relaunch in a fresh shell, since some shells cache env that the CLI inherits.

Step 8 (optional): Capture the debug log

If /doctor and /hooks still leave you guessing, run:

claude --debug 2>&1 | grep -i -E 'settings|hook|permission'

It prints the file load order and which scope supplied which value.

How to confirm it’s fixed

  • /doctor reports no invalid entries and lists your file as a source.
  • /hooks shows your hook with the correct source and matcher.
  • The permission rule actually allows or denies as configured (no surprise prompt).
  • printenv MY_VAR inside a Bash tool call returns the expected value.
  • Saving a further edit takes effect live (for permissions/hooks), confirming the file is being watched.

Long-term prevention

  • Add "$schema": "https://json.schemastore.org/claude-code-settings.json" to every settings file so your editor catches typos and unknown keys.
  • Validate with jq after editing; wire it into a project pre-commit hook.
  • Keep a settings.example.json in your dotfiles repo documenting each block.
  • Don’t put the same hook in both user-global and project-local files; pick one home.
  • Use .claude/settings.local.json for personal overrides that must not be committed.
  • Skim Claude Code release notes for renamed keys and deprecated paths.

Common pitfalls

  • Adding a // or # comment into JSON. JSON has no comments; Claude Code is not forgiving.
  • A trailing comma after the last array or object element. Strict JSON rejects it.
  • An editor that injects smart quotes or a BOM.
  • Committing settings.local.json with personal hooks by accident.
  • Assuming a restart is required and not realizing your model change is the one case that actually needs /model.
  • Expecting a project allow to override a user deny — permission lists merge, and deny always wins.

FAQ

  • Which settings file wins if I have both? For scalar values: managed > command-line args > project-local > project-shared > user. The most specific layer wins per key. Permission lists are the exception — they merge.
  • Do I need to restart Claude Code after editing settings? Usually no. permissions, hooks, and apiKeyHelper hot-reload into the running session as of June 2026. Only model (use /model) and outputStyle (/clear or restart) need action.
  • Why does my allow rule still prompt? Rules evaluate deny -> ask -> allow, and the lists merge across all scopes. A deny or ask rule in any file beats your allow.
  • Can I have comments in settings.json? No, strict JSON only. Use a separate notes file, or a _comment key, to annotate.
  • Why is my env var still empty? It is in the wrong scope, or its value is not a quoted string. Every env value must be a string.
  • How do I see exactly which file a setting came from? Run /doctor for an overview, /hooks for hook sources, or claude --debug for the raw load order.

External references:

Tags: #Claude Code #settings #Troubleshooting #CLI