If you've been using Claude Code for more than a week, you've likely edited both CLAUDE.md and .claude/settings.json and, at some point, wondered where specific configurations should go. While they appear similar and both configure Claude's behavior, they control entirely different aspects. Mixing them up invariably leads to frustrating results. Here's a complete breakdown.
The Mental Model
CLAUDE.md = Claude's Brain
settings.json = Claude's Permissions
CLAUDE.md is a natural language file where you write instructions, context, constraints, and preferences. Claude processes it much like a colleague reads a briefing document.
settings.json, on the other hand, is machine configuration. It dictates which tools Claude is allowed to use, what commands it can run, and what actions it's explicitly blocked from performing.
What CLAUDE.md Controls
Content that belongs in CLAUDE.md includes:
- Project context: E.g., "This is a Next.js 14 app using Prisma + PostgreSQL."
- Architecture decisions: E.g., "All API routes live in /app/api/; Database models are in /prisma/schema.prisma; Never create new /pages/ routes — we're on App Router."
- Code style: E.g., "TypeScript strict mode always on; Prefer async/await over .then() chains; Use Zod for all input validation."
- Response format: E.g., "Give me code first, explanation after; Skip obvious comments; When suggesting a fix, show the before AND after."
- Tech stack and architecture details
- Coding conventions and style preferences
- Business domain context (what the app does, who uses it)
- Response format preferences
- "Always/never do" instructions
- Commands to run for common tasks
What settings.json Controls
A typical settings.json structure defines permissions:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(cat:*)",
"Bash(grep:*)",
"Bash(find:*)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push:*)",
"Bash(curl:*)",
"WebFetch"
]
}
}Content that belongs in settings.json includes:
- Which Bash commands Claude can run without explicit approval
- Which tools are blocked entirely
- Auto-approve settings for trusted operations
- Model preferences (if you are a Pro subscriber)
The Key Difference: CLAUDE.md is Read, settings.json is Enforced
Claude can potentially choose to ignore something in CLAUDE.md if it 'thinks' it knows better (though it shouldn't, it's a possibility). Settings.json, however, acts as a hard fence — Claude is literally incapable of executing a command that is listed in the deny section.
This fundamental difference dictates placement:
- Style preferences belong in CLAUDE.md — you want Claude to understand and apply them, not be mechanically blocked.
- Safety constraints belong in settings.json — you want actual enforcement, not mere suggestions.
Real-world Example: Preventing Accidental Deploys
The Wrong Approach — Putting it in CLAUDE.md:
# IMPORTANT
Never run `git push`
With this approach, Claude might, under certain circumstances, 'forget' or 'decide' to override this instruction.
The Correct Approach — Putting it in settings.json:
"deny": ["Bash(git push:*)"]
This configuration guarantees that Claude cannot execute the git push command under any circumstances, providing true security.