CLAUDE LABJP
MCP — Claude now supports more of the MCP 2026-07-28 spec, bringing a stateless core, stronger OAuth and OIDC authorization, and versioned extensions for Apps and TasksDESIGN — /design arrived as a research preview on August 17. Hand it an idea, a screenshot, or an existing design and it returns editable artboards in Claude DesignOUTPUT — A Concise output style now leads with the result, which helps when you would rather not read past the preamblePERMISSIONS — Auto mode is the new default, and you write allow and deny rules as plain sentences rather than patternsLIMITS — The 50 percent increase to weekly limits runs through August 31 for Pro, Max, Team, and seat-based Enterprise plans. Six days remainRESUME — Sessions that hit a usage limit now continue automatically once the limit resets, and protections against credential leaks have been strengthenedMCP — Claude now supports more of the MCP 2026-07-28 spec, bringing a stateless core, stronger OAuth and OIDC authorization, and versioned extensions for Apps and TasksDESIGN — /design arrived as a research preview on August 17. Hand it an idea, a screenshot, or an existing design and it returns editable artboards in Claude DesignOUTPUT — A Concise output style now leads with the result, which helps when you would rather not read past the preamblePERMISSIONS — Auto mode is the new default, and you write allow and deny rules as plain sentences rather than patternsLIMITS — The 50 percent increase to weekly limits runs through August 31 for Pro, Max, Team, and seat-based Enterprise plans. Six days remainRESUME — Sessions that hit a usage limit now continue automatically once the limit resets, and protections against credential leaks have been strengthened
Articles/Claude Code
Claude Code/2026-08-25Intermediate

A One-Letter Typo in settings.json Is Ignored Without a Single Warning

I diffed claude doctor output between a settings.json with misspelled keys and a correct one. There was no difference at all. Here is what actually gets validated, what slips through, and a small check that catches typos before they cost you a day.

Claude Code234settings.json6configuration9troubleshooting88automation104

I handed Claude Code 2.1.237 a settings.json with a single letter wrong in a key name, ran claude doctor, and diffed the output against the correct file.

There was no difference. Not a warning, not a note. The file loads, the parser is happy, and the misspelled line quietly evaporates.

As an indie developer with several sites handed off to unattended jobs, I edit these files more often than I expected to, and this is the failure mode I fear most. Broken configuration announces itself. Silently discarded configuration lets you believe a setting is in force for days.

Everything below was measured on Claude Code 2.1.237 (linux-x64).

The correct file and the misspelled file produce byte-identical output

I set up two directories. One of them collects four typos I have genuinely made before.

// A: .claude/settings.json (with typos)
{
  "outputStile": "Concise",
  "cleanupPeriodDay": 3,
  "permissionmode": "plan",
  "permissions": { "denny": ["Bash(rm -rf *)"] }
}
// B: .claude/settings.json (correct)
{
  "outputStyle": "Concise",
  "cleanupPeriodDays": 3,
  "permissions": { "deny": ["Bash(rm -rf *)"] }
}

outputStile swaps y for i. cleanupPeriodDay drops the plural s. permissionmode loses the interior capital. permissions.denny doubles the n. Every one of them is valid JSON, and every one of them reads as fine at a glance.

I ran claude doctor in each directory and compared. That subcommand reads the settings files in the current directory without a trust prompt, which makes it convenient for exactly this kind of check.

cd A && claude doctor < /dev/null > ../doctorA.txt 2>&1
cd ../B && claude doctor < /dev/null > ../doctorB.txt 2>&1
diff ../doctorA.txt ../doctorB.txt && echo "identical"

The output was identical. The deny rule I wrote as denny never appears anywhere, and never gets mentioned as missing. A guardrail I intended to put in front of a destructive command simply is not there.

Only value types are actually validated

If key names slip through, what does get caught? I varied the kind of mistake and measured five cases.

Kind of mistakeExampleWhat doctor saysExit code
JSON syntax errortrailing commaInvalid settings / Invalid or malformed JSON0
Wrong type on a known key"cleanupPeriodDays": "three"Expected number, but received string0
Wrong type, nested"permissions": { "deny": "Bash(rm -rf *)" }Expected array, but received string0
Value that does not exist"permissionMode": "planz"silence0
Misspelled key nameoutputStile and three otherssilence (output identical to the correct file)0

Type checking is real and it is good. Put a string where a number belongs and you get Expected number, but received string with the exact path. The nested case behaves the same way. You can rely on this part.

What you cannot rely on is value validity. permissionMode: "planz" passes without comment, presumably because it is a string and the schema asks for a string. Do not assume enum values are checked.

And key names are invisible no matter how you mangle them. All five cases exited 0, which matters more than it looks. I will come back to it.

The MCP config does warn about the same mistake

This is not a blanket personality trait of the CLI. Make the equivalent typo in an MCP server definition and you get told.

// .mcp.json — "command" misspelled as "commnad"
{ "mcpServers": { "demo": { "commnad": "node", "args": ["server.js"] } } }
$ claude mcp list
No MCP servers configured. Use `claude mcp add` to add a server.

MCP config diagnostics ⚠
[Contains warnings] Project config (shared via .mcp.json)
 └ [Warning] [demo] mcpServers.demo: Skipped — invalid MCP server config for "demo":
   command: expected string, received undefined

It never names commnad as an unknown key, but it notices that the required command is missing, skips the server, and tells you which one. When a schema has required fields, a misspelled key surfaces naturally as an absence.

settings.json has no required fields. Every key is optional, so any file is a valid file, including one where half your intent has been thrown away. The design makes sense. It just means the net has to be yours.

Catching misspelled keys yourself

I could not find a way to pull the official key list programmatically, so I keep a small allowlist of the keys I actually use and compare against that. Trying to enumerate everything would rot within a month; a deliberately narrow list stays accurate.

# known_keys.txt — only the keys you actually use. Add new ones on purpose.
outputStyle
permissionMode
cleanupPeriodDays
permissions
permissions.deny
permissions.allow
permissions.ask
env
model

The checker is short enough to read in one sitting.

#!/usr/bin/env python3
"""Detect misspelled key names in settings.json.
claude doctor never warns about unknown keys, so compare against a known-key list yourself.
Usage: python3 check_settings_keys.py <settings.json> <known_keys.txt>
"""
import difflib, json, sys, re
 
def flatten(obj, prefix=""):
    """Collapse nested keys into dotted paths like permissions.deny.
    Only key names matter here, so we do not descend into arrays or hook bodies."""
    out = []
    if isinstance(obj, dict):
        for k, v in obj.items():
            path = f"{prefix}{k}"
            out.append(path)
            if isinstance(v, dict):
                out.extend(flatten(v, path + "."))
    return out
 
def normalize(k):
    """Ignore case and separators so permissionmode matches permissionMode."""
    return re.sub(r"[^a-z0-9]", "", k.lower())
 
def main(settings_path, known_path):
    with open(known_path, encoding="utf-8") as f:
        known = [l.strip() for l in f if l.strip() and not l.startswith("#")]
    known_norm = {normalize(k): k for k in known}
 
    try:
        with open(settings_path, encoding="utf-8") as f:
            data = json.load(f)
    except json.JSONDecodeError as e:
        print(f"FAIL {settings_path}: not readable as JSON ({e})")
        return 1
 
    typos, unknowns = [], []
    for key in flatten(data):
        if key in known:
            continue
        hit = known_norm.get(normalize(key))
        if hit is None:
            # Keys that differ by one or two characters are typos too
            near = difflib.get_close_matches(normalize(key), known_norm.keys(), n=1, cutoff=0.8)
            hit = known_norm[near[0]] if near else None
        if hit:
            typos.append((key, hit))     # spelled almost right = almost certainly a typo
        else:
            unknowns.append(key)         # genuinely unknown = possibly a new setting
 
    for wrong, right in typos:
        print(f"FAIL {settings_path}: '{wrong}' looks like a typo for '{right}'")
    for key in unknowns:
        print(f"??   {settings_path}: '{key}' is not in the list (add it if the setting is real)")
 
    if not typos and not unknowns:
        print(f"OK   {settings_path}")
    return 1 if typos else 0   # fail on typos only; let unknown keys through
 
if __name__ == "__main__":
    sys.exit(main(sys.argv[1], sys.argv[2]))

My first version did not have the three difflib lines. Normalizing case and separators alone caught only permissionmode out of the four, because outputStile and cleanupPeriodDay and permissions.denny are genuinely different strings once normalized.

Catching a one-character difference requires similarity, not equality. With difflib.get_close_matches at cutoff=0.8, all four get named:

$ python3 check_settings_keys.py A/.claude/settings.json known_keys.txt
FAIL A/.claude/settings.json: 'outputStile' looks like a typo for 'outputStyle'
FAIL A/.claude/settings.json: 'cleanupPeriodDay' looks like a typo for 'cleanupPeriodDays'
FAIL A/.claude/settings.json: 'permissionmode' looks like a typo for 'permissionMode'
FAIL A/.claude/settings.json: 'permissions.denny' looks like a typo for 'permissions.deny'
$ echo $?
1

False positives were the thing I actually worried about. A check that fails the moment you adopt a legitimate new setting gets disabled within a week. I added statusLine and enableAllProjectMcpServers to test that:

$ python3 check_settings_keys.py H/.claude/settings.json known_keys.txt
??   H/.claude/settings.json: 'statusLine' is not in the list (add it if the setting is real)
??   H/.claude/settings.json: 'statusLine.type' is not in the list (add it if the setting is real)
??   H/.claude/settings.json: 'enableAllProjectMcpServers' is not in the list (add it if the setting is real)
$ echo $?
0

They are visible, but the exit code stays 0. Fail on typos, report unknowns. That split is what makes the check survivable in daily use.

doctor exits 0 even when your settings never loaded

There is one more trap if you plan to automate any of this. As the table shows, claude doctor returns 0 regardless — including when a trailing comma means the entire file was discarded.

#!/usr/bin/env bash
# claude doctor exits 0 even for broken settings. Read the output and fail yourself.
set -uo pipefail
out="$(claude doctor < /dev/null 2>&1)"
echo "$out"
if grep -q 'Invalid settings' <<< "$out"; then
  echo "FAIL: settings were not loaded (see Invalid settings above)" >&2
  exit 1
fi
exit 0

Across the correct file, the trailing-comma file, and the wrong-type file, this returned 0, 1, and 1. The diagnosis was already there; the only missing piece was treating it as a failure.

Put the two together and the shape of the check is settled. Let doctor own syntax and types, let your own list own key names, and let both speak through exit codes so they fit in a pre-commit hook or the preflight step of an unattended job.

If the file is broken badly enough that Claude Code will not start at all, the triage path is different, and I wrote that one up separately in When a Broken settings.json Stops Claude Code From Starting. This piece is about the stage before that, where startup is fine and only your intent is missing.

Confirm the behavior, not just the file

To be straight about the limits: this check protects the keys you already know about. Anything outside the list passes as ??, and value validity is never examined. The permissionMode: "planz" file sails past both my checker and doctor.

So I pair it with a habit. Whenever I add a setting, I confirm it once by behavior. Wrote a deny rule? Try the command and watch it get refused. Changed the output style? Look at the shape of the next response. It costs under a minute, and it is the only real evidence that what you wrote arrived.

Finding things that fail quietly always comes down to the same move: decide the expected count or the expected reaction in advance, then compare it against what actually happened. I applied the same idea to a batch job that was skipping every file in One Space in a Folder Name Turned 80 Checks Into Zero.

Start by opening one settings.json and reading the key names out loud. Plural s present? Interior capitals intact? That alone may surface a setting you thought was in force.

This script started life the day I found exactly one such typo, and it has grown from there. Thank you for reading.

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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-07-01
My Claude Code Hooks Stopped Firing After an Update — the Hyphenated Matcher Exact-Match Change in v2.1.195
In Claude Code v2.1.195, hook matchers containing a hyphen switched from partial match to exact match, silently disabling an existing PreToolUse hook. Here is how I isolated the cause and how to write matchers that won't break.
Claude Code2026-05-11
Claude Code MCP Server Won't Start — How to Fix "spawn npx ENOENT" and PATH Issues
You configured an MCP server in Claude Code's settings.json, but it never starts — just "spawn npx ENOENT" or "spawn uvx ENOENT" errors. The culprit is a PATH mismatch between your shell and Claude Code's spawning environment. Here's how to diagnose and fix it.
Claude Code2026-04-12
CLAUDE.md Not Working? A Complete Troubleshooting Guide for Claude Code
Claude Code ignoring your CLAUDE.md settings? This guide covers the most common causes — wrong file location, formatting issues, conflicting configs, and more — with clear fixes for each.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →