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
| Symptom | Most likely cause | Jump to |
|---|---|---|
/doctor reports an invalid entry or parse error | JSON syntax error or unknown key | Step 1 |
| Change works for a teammate but not you (or vice versa) | Edited the wrong scope / precedence shadowing | Step 2 |
/hooks shows “No hooks configured” or 0 matchers | Wrong file, wrong event/matcher shape, or syntax error | Step 3 |
Permission still prompts despite an allow rule | A deny or ask rule wins; rules merge across scopes | Step 4 |
env var is empty inside a Bash tool call | Wrong scope, or a non-string value | Step 6 |
| Nothing loads; file looks fine | File owned by root / unreadable | Step 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
jqis installed; you will use it to validate.
Information to collect
- Claude Code version:
claude --version. - Who is running Claude Code:
whoami. ls -la ~/.claude/andls -la .claude/from your project.- The full contents of every involved settings file.
jq . ~/.claude/settings.json(note any error)./doctoroutput, and/hooksif 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
/doctorreports no invalid entries and lists your file as a source./hooksshows your hook with the correct source and matcher.- The permission rule actually allows or denies as configured (no surprise prompt).
printenv MY_VARinside 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
jqafter editing; wire it into a project pre-commit hook. - Keep a
settings.example.jsonin 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.jsonfor 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.jsonwith personal hooks by accident. - Assuming a restart is required and not realizing your
modelchange is the one case that actually needs/model. - Expecting a project
allowto override a userdeny— permission lists merge, anddenyalways 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, andapiKeyHelperhot-reload into the running session as of June 2026. Onlymodel(use/model) andoutputStyle(/clearor restart) need action. - Why does my
allowrule still prompt? Rules evaluatedeny->ask->allow, and the lists merge across all scopes. Adenyoraskrule in any file beats yourallow. - Can I have comments in settings.json? No, strict JSON only. Use a separate notes file, or a
_commentkey, to annotate. - Why is my env var still empty? It is in the wrong scope, or its value is not a quoted string. Every
envvalue must be a string. - How do I see exactly which file a setting came from? Run
/doctorfor an overview,/hooksfor hook sources, orclaude --debugfor the raw load order.
Related
- Claude Code hook blocks edit unexpectedly
- Claude Code permissions prompt loop
- Claude Code skill not discovered
- Claude Code MCP call timeout
- Claude Code asks too many questions
External references: