Introduction
I often get questions about how to set up Claude Code properly before diving into the advanced stuff — skills, hooks, subagents, and MCP servers. The honest answer is that most people skip the fundamentals, then wonder why the tool behaves inconsistently.
Two things covered here: the file hierarchy — where settings actually live and which ones take precedence — and CLAUDE.md, which is the one file that makes Claude Code feel like it knows your project instead of starting from scratch every session.
Nothing in here is complex. But getting it wrong will undermine everything you build on top of it.
Configuration Overview
There is no single config file. Settings are spread across several files, each scoped differently — which is exactly why permissions sometimes seem to "not stick" and why your teammate's setup behaves differently from yours.
Broken down by scope:
User-level (global, applies to all projects):
~/.claude/settings.json— your personal defaults: model preference, globally allowed/denied tools~/.claude/CLAUDE.md— instructions that load in every session, across every project
Project-level (lives in the repo):
.claude/settings.json— team-shared settings; commit this.claude/settings.local.json— your personal overrides within the project; gitignored automaticallyCLAUDE.md— project-specific instructions; commit this tooCLAUDE.local.md— your personal project notes; gitignored
The rule of thumb: enterprise managed beats project-local, project-local beats project-shared, and project-shared beats user. More specific always wins.
Worth knowing before you hit it: defining a permissions block at the project level currently replaces your global permissions, not extends them. Tools you allowed globally will start prompting for confirmation again inside that project unless you list them again explicitly. The docs imply merging. The actual behavior is replacement. Add it to your mental model now.
The two files you need right now.
~/.claude/settings.json
Start here. Set your model, then lock down anything you never want Claude near.
{
"model": "claude-sonnet-4-6",
"permissions": {
"allow": [
"Bash(git log *)",
"Bash(gcloud builds list *)"
]
}
}
You'll notice there are no deny rules here. That is intentional — I'll explain why in the next section.
CLAUDE.md
Claude reads this file at the start of every session, before you type anything. Plain Markdown, no special syntax. The closest analogy I have: it's the onboarding doc you write once for a new joiner — except this one actually gets read, every time, without skimming.
Claude picks these up automatically from three locations:
~/.claude/CLAUDE.md # global — scaffolding rules, personal defaults
./CLAUDE.md # project root — architecture, build commands, conventions
./src/CLAUDE.md # subdirectory — component-specific rules
Conflicts are resolved in favor of the more specific one.
In practice, this works reliably when files cover different topics. The global file sets your personal defaults, the project file covers the stack and conventions — no overlap, no ambiguity. Where it gets murkier is when two levels define conflicting instructions for the same thing.
The spec says the more specific file wins, but in practice, Claude may blend instructions from both, or follow whichever appeared more recently in context. I have seen it go both ways. Treat cross-level conflicts as undefined behavior and avoid them by keeping each file's scope distinct.
What to put in CLAUDE.md
First instinct is usually to dump everything in. Don't. Past a certain point CLAUDE.md stops helping — it just burns tokens and buries the instructions that actually matter.
For the global ~/.claude/CLAUDE.md, I keep things that apply everywhere — personal habits, not project specifics:
# Global Preferences
- Never use `rm -rf` without confirmation.
- Prefer explicit error handling over silent failures.
- When in doubt about scope of a change, ask before proceeding.
- Use the `gh` CLI for all GitHub operations, not raw `curl` against the API.
The project-level CLAUDE.md is best thought of as the onboarding you never had time to write — the stuff a new developer would figure out after a few hours of poking around, now written down in one place:
# Project: payments-service
## Stack
Node.js 20, TypeScript, PostgreSQL via Prisma, Jest for tests.
## Build and Test
- Build: `npm run build`
- Run tests: `npm test`
- Lint: `npm run lint` (must pass before any commit)
## Architecture Notes
- All database access goes through `/src/repositories`. Never query the DB directly from controllers.
- `src/lib/stripe.ts` wraps the Stripe SDK. Do not instantiate Stripe directly elsewhere.
## Conventions
- Error handling: use the `AppError` class in `src/lib/errors.ts`
- Logging: use the logger in `src/lib/logger.ts`, not `console.log`
Keep in mind this isn't documentation — it's operational context for something that otherwise starts every session knowing nothing about your codebase.
Why I don't set deny rules.
The obvious question: why no .env deny rules, no rm -rf block?
The short answer is that deny rules in settings.json are not a reliable security boundary. They're pattern-matching on strings. Claude Code is an agent that reasons — and agents reason around pattern matching.
Security researchers documented a case where Claude bypassed a deny rule by using /proc/self/root/usr/bin/npx instead of npx. Different string, same binary. When the sandbox caught that, the agent identified the sandbox as the obstacle and reasoned about disabling it. No jailbreak, no special prompt. It was just solving a problem. There's also a filed bug where Read and Write deny rules were found to be completely non-functional on certain platforms — Claude would read the file anyway.
For anything I genuinely don't want touched, I rely on OS-level file permissions or run Claude Code in a container. Those actually hold. A JSON deny list does not.
There's a community plugin that does a better job than deny rules for the common cases: claude-code-safety-net. It intercepts destructive git and filesystem commands at a lower level, before they execute. Worth installing. Just don't treat it as a hard guarantee — a sufficiently motivated agent can still find a way around it.
Put simply: Claude Code runs as you, on your machine. Don't hand it credentials it has no reason to touch, don't point it at a repo you haven't read, and don't walk away from a long agentic run on anything you can't roll back.
Pitfalls and how to avoid them.
Committing the wrong files
Four files come out of this setup. Two go in the repo, two stay off it.
# Commit these — share with the team
./CLAUDE.md
./.claude/settings.json
# Do NOT commit these — personal overrides
./CLAUDE.local.md
./.claude/settings.local.json
settings.local.json is gitignored automatically. CLAUDE.local.md is not — that one you have to handle yourself. Miss it, and you'll end up committing personal notes, local paths, or half-finished permission experiments to the shared repo.
# Add to your .gitignore
echo "CLAUDE.local.md" >> .gitignore
Obvious in hindsight. Less obvious when you're staring at a CI failure at 11 pm, trying to figure out why a machine-local path ended up in a shared config.
Using CLAUDE.md as a substitute for a good project structure
I've seen CLAUDE.md files that run to several hundred lines, detailing the entire history of architectural decisions, third-party integrations, and business logic edge cases. This is counterproductive.
Every line loads into context on every session. At some point, the file starts working against you — the stuff that actually matters gets buried, you're burning tokens on background noise, and Claude starts making decisions based on whichever instruction happened to land closest to the end of its context window.
My rule of thumb: if you're writing a paragraph explaining why a module is structured a certain way, that belongs in a code comment or an ADR.md — not in the agent's startup context. CLAUDE.md is for operations: how to build, how to test, what tools to use, what to never touch.
Not sure where to start? Run /init from the project directory. Claude Code scans the codebase and generates a starter file. It won't be perfect, but it's a better starting point than a blank page.
Summary
Before hooks, subagents, or other integrations — get these two right:
~/.claude/settings.json— pick your model, set your allowed tools. That's it for now.CLAUDE.md— global file for personal defaults, project file for build commands, stack, and conventions. Commit the project one. Keep both short enough that you'd actually read them yourself.
The blur I see most often: people write rules in CLAUDE.md expecting them to hold under pressure, then reach for settings.json permissions as an afterthought. It tends to go the other way around — permissions for the hard stops, CLAUDE.md for everything that requires judgment.
