●DESIGN — Claude Design gets a major update: design-system imports, direct canvas editing, and more export formats●CODE — Claude Design can start from your local codebase and hand a design off to Claude Code to implement●FABLE — Fable 5, a Mythos-class model made safe for general use, is now available in Claude Code v2.1.170●FIX — Mid-stream connection drops now preserve partial responses instead of showing a raw error●SCROLL — A new wheelScrollAccelerationEnabled setting disables mouse-wheel scroll acceleration in fullscreen●TIER — The Claude Design beta is available to Pro, Max, Team, and Enterprise customers●DESIGN — Claude Design gets a major update: design-system imports, direct canvas editing, and more export formats●CODE — Claude Design can start from your local codebase and hand a design off to Claude Code to implement●FABLE — Fable 5, a Mythos-class model made safe for general use, is now available in Claude Code v2.1.170●FIX — Mid-stream connection drops now preserve partial responses instead of showing a raw error●SCROLL — A new wheelScrollAccelerationEnabled setting disables mouse-wheel scroll acceleration in fullscreen●TIER — The Claude Design beta is available to Pro, Max, Team, and Enterprise customers
When a Broken settings.json Stops Claude Code From Starting — Safe Mode and How to Split Your Config
How to find which config layer is broken when a settings.json syntax error stops Claude Code from starting, recover in minutes, and structure your settings so an automated pipeline can't quietly break itself.
A single stray comma in a config file once stopped Claude Code from starting at all. If you hit that even once, it changes how you think about configuration. As an indie developer I run a pipeline that updates four sites automatically alongside my apps, and one morning the scheduled run had quietly stalled — I'd left a trailing comma in a hooks block I appended to .claude/settings.json. There was an error, but which file in which layer caused it wasn't obvious. That feeling — minutes draining away on triage — is the real trap with configuration.
Recent versions of Claude Code added a behavior where, when it detects a broken config, it isolates that single file and keeps starting in a degraded "safe mode." That's convenient, but if you don't understand why your usual permission rules suddenly aren't applying, it creates a different kind of confusion. Here I'll lay out what happens when config breaks, where to start triaging, and — more importantly — how to structure your settings so an automated workflow doesn't break itself in the first place.
What Claude Code does when config breaks
The first thing to internalize is that Claude Code's configuration isn't all-or-nothing. Settings are loaded from several files, and higher-priority ones override lower-priority ones. From highest to lowest:
This layered structure is exactly why the behavior on breakage feels counterintuitive. Older builds could refuse to launch when any file was malformed; recent ones quarantine the broken config instead. When Claude Code finds a file it can't parse as JSON, it drops that one file from the load set and keeps starting in safe mode using the remaining valid settings.
The easy misreading here is "it started, so my settings must be applied." In reality, the permissions.deny and hooks you wrote in the quarantined file are being ignored entirely. In an automated context that produces a half-working state: a command you meant to deny falls back to ask and blocks on approval, or a hook you expected never fires and only a log remains. The "scheduled run quietly stalled" symptom I hit was exactly this pattern — a nasty gotcha when it strikes a production pipeline.
Whether you booted into safe mode shows up in the warning at startup and in /doctor, which tells you which settings file it flagged. That's the fastest place to look. I've written up how to read /doctor itself in Triaging config trouble in three minutes with /doctor.
Pinpointing which layer is broken
Recovery starts with identifying the broken file. There are at most five settings files, so mechanically validating each one as JSON surfaces the culprit immediately. This is the one-liner I run first: it parses each file with jq in priority order and bluntly flags the ones that fail.
# Validate every settings file that could be loaded, as JSON.# Only broken files are reported as "INVALID".for f in \ "/Library/Application Support/ClaudeCode/managed-settings.json" \ "/etc/claude-code/managed-settings.json" \ "$PWD/.claude/settings.local.json" \ "$PWD/.claude/settings.json" \ "$HOME/.claude/settings.json"; do [ -f "$f" ] || continue if jq empty "$f" 2>/dev/null; then echo "OK $f" else echo "INVALID $f" # Show the error with line/column of the break jq empty "$f" fidone
Expected output looks like this — only the broken file is INVALID, and jq tells you exactly where parsing fell apart.
OK /Users/you/project/.claude/settings.json
INVALID /Users/you/.claude/settings.json
jq: error (at /Users/you/.claude/settings.json:14): Expected another key-value pair at line 14, column 3
At that point the offending file and line are settled. If jq isn't installed, Python's standard library does the same job.
# Fallback validation when jq is unavailablepython3 - "$HOME/.claude/settings.json" << 'PY'import json, syspath = sys.argv[1]try: with open(path) as fp: json.load(fp) print("OK", path)except json.JSONDecodeError as e: print(f"INVALID {path}: {e}")PY
The most common triage mistake is forgetting the high-priority enterprise managed policy exists. On a team-provisioned Mac, /Library/Application Support/ClaudeCode/managed-settings.json overrides your ~/.claude/settings.json, so when "I edited my user settings but nothing changed," suspect that top-level file first. Command-line arguments like --model also win over settings, so check whether a stale flag lingers in your launch script.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦If a settings.json syntax error has ever left Claude Code refusing to start, you'll understand what safe mode actually does and be able to recover in minutes
✦You'll get the exact commands and steps to pinpoint whether the broken config lives in the enterprise, user, project, or local layer
✦You'll be able to split and pre-validate your settings.json so an automated pipeline never ships a broken config in the first place
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Once you know the culprit, don't edit blindly — first build a footing you can roll back to. Settings files aren't always under version control, so I always take a timestamped backup before touching anything.
# Stash the broken file before editing (prevents overwrite accidents)BROKEN="$HOME/.claude/settings.json"cp "$BROKEN" "${BROKEN}.broken.$(date +%Y%m%d-%H%M%S)"# Mechanically check the "usual ways people break it"grep -nE ',\s*[}\]]' "$BROKEN" && echo "^ possible trailing comma"grep -nE '^\s*//' "$BROKEN" && echo "^ JSON does not allow comments"
In my experience, the vast majority of broken configs come down to three causes: a trailing comma, a comment written into JSON, and an unclosed string quote. The grep above flushes out those top three at once. After fixing, run jq empty again before relaunching, and only restart Claude Code once it's green.
When you truly can't trace the cause, the approach I personally find most reliable is to binary-search it back in, step by step:
Swap the broken file for {} (an empty valid JSON object) and confirm startup passes with no config. If it does, the problem is in the content.
Restore only the permissions block from your backup, re-validate with jq empty, and relaunch.
Restore the env block, then hooks, one at a time, to find exactly which block breaks it again.
# Swap in a minimal valid settings to test whether startup passesecho '{}' > "$HOME/.claude/settings.json"# If startup passes now, the problem is somewhere in the content.# Restore from the stash block by block.
{} (an empty object) is perfectly valid config, so if startup passes the problem is definitely in the content. Safe mode is only first aid: while a file stays quarantined, its permissions.deny isn't enforced, which is a dangerous state — so I'd strongly recommend not relaxing just because it booted. Restore to a fully valid config.
Splitting your config so it doesn't break in the first place
This is the part that matters more than recovery. If you cram permissions, environment variables, and hooks into one giant settings.json, a single mistake invalidates the whole file and drags unrelated settings down with it. After getting burned in my four-site automation, I restructured my config so that breakage stays local. Because I run app updates for the App Store and Google Play and article generation from the same machine, a config accident left unattended spreads across several systems at once.
The idea is simple: split by change frequency and risk. The rarely-touched permission foundation goes in the project-shared .claude/settings.json, while frequently-edited experimental settings and personal overrides are isolated in .claude/settings.local.json. That way, if you break local settings mid-experiment, the shared permissions.deny — your safety net — survives.
The benefit of this split is that the rules you most want to protect — denying destructive commands and secret-file reads — ride on the file least likely to break. Breaking an experimental allow in settings.local.json leaves the safety foundation untouched. Conversely, writing deny into the experiment file is a mistake: I'd recommend keeping the safety net in the most stable file. (For pinning the model against unintended auto-upgrades, I cover that separately in Pinning your run model with enforceAvailableModels.)
The "I fixed it but nothing changed" case on team-managed Macs
You rarely hit this in solo use, but on a team-provisioned Mac there's an extra layer of subtlety. The enterprise managed policy (managed-settings.json) loads at the top priority, so if it defines permissions.deny, no amount of allow you add in ~/.claude/settings.json will override it. When "I fixed my own settings but the behavior didn't change," the shortcut is to check for a managed policy first.
# Check whether a managed policy exists and what it enforces, firstMGD="/Library/Application Support/ClaudeCode/managed-settings.json"if [ -f "$MGD" ]; then echo "Managed policy present: $MGD" jq '.permissions // {}' "$MGD" # show what's being enforcedelse echo "No managed policy — user/project settings are in effect"fi
A managed policy is enforced on purpose, so you shouldn't quietly rewrite it. Operations it denies are meant to be honored as an organizational rule, and I'd rebuild the rest of my config around that assumption. Put differently, checking this one file before you suspect your own settings.json can cut a lot of triage time.
In automation, make "validate before you write" a mechanism
Being careful by hand won't prevent accidents that happen inside an automated pipeline. My scheduled runs include steps that rewrite settings with sed or cat, so I always route them through a gate that stops the moment a written JSON is broken — catching it right after the file is updated, not at push time.
# After rewriting settings, validate immediately and roll back if brokenwrite_settings() { local target="$1" tmp tmp="$(mktemp)" cat > "$tmp" # receive new content from stdin if jq empty "$tmp" 2>/dev/null; then cp "$target" "${target}.prev" # stash the last valid version mv "$tmp" "$target" echo "✅ settings updated (validated): $target" else echo "🛑 invalid JSON, write aborted: $target" jq empty "$tmp" # show error location rm -f "$tmp" return 1 fi}# Usage: pass new settings via stdincat << 'JSON' | write_settings "$HOME/.claude/settings.json"{ "permissions": { "deny": ["Bash(rm -rf /*)"] } }JSON
This function writes to a temp file, validates with jq empty, and only mvs to the live file when it passes. On failure it never touches the live file and keeps the last valid version (.prev), so broken content never reaches production. Once I made "validate, then replace" a mechanism, the config-induced stalls that used to hit me a couple of times a month dropped to nearly 0%. This isn't specific to settings files either — it mirrors the design principle across my whole pipeline, where generated articles pass a quality gate before they're pushed. It all comes down to one thing: anything that might be broken gets rejected before it reaches production.
If I'd recommend one practice, it's this: run your current ~/.claude/settings.json through jq empty once. Even if it's working fine today, confirming its validity now makes the next config change feel completely different. I hope this helps anyone else handling settings inside an automated workflow.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.