3. Configuration Hierarchy
The Three-Layer Stack
┌──────────────────────────────────────────────┐
│ Global (~/.claude/CLAUDE.md) │
│ Identity, tone, universal tools, cross- │
│ project conventions │
├──────────────────────────────────────────────┤
│ Project (./CLAUDE.md + path modules) │
│ Architecture decisions, stack conventions, │
│ team rules, deployment procedures │
├──────────────────────────────────────────────┤
│ Session (inline instructions, flags) │
│ Ad-hoc overrides, experiment constraints, │
│ one-off task parameters │
└──────────────────────────────────────────────┘Later layers override earlier ones. A session instruction can override a project rule; a project rule can override a global default. This gives you escape hatches without requiring permanent changes to shared configuration.
Global Configuration
Location: ~/.claude/CLAUDE.md
What belongs here:
- Identity and communication style preferences
- Universal tool preferences (RTK, preferred CLI tools)
- Cross-project coding conventions (commit message format, PR style)
- Security constraints that apply everywhere
- Tone and output format defaults
What does not belong here:
- Project-specific architecture decisions
- Stack-specific rules (React hooks, Prisma patterns)
- Deployment or environment specifics
- Anything that changes per project
Size target: Keep global configuration under 200 lines. This is your always-on overhead for every session in every project. Bloating it hurts all projects equally.
# Example: Minimal effective global CLAUDE.md
## Communication
- Respond in the same language the user writes in
- Prefer direct answers over preamble
- No em dashes in written output
## Git
- Commit messages: imperative mood, <72 chars subject line
- Never commit without being asked
## Code Style
- Prefer explicit error handling over silent failure
- Add TODO comments only when referencing a tracked issueProject Configuration
Location: ./CLAUDE.md (project root)
What belongs here:
- Technology stack and versions in use
- Architecture decisions and their rationale
- Team conventions specific to this codebase
- File organization patterns
- Testing requirements and coverage targets
- Security constraints specific to this project
- Path-scope imports for subsystem modules
Structure pattern:
# Project: [Name]
## Stack
- Language: TypeScript 5.3
- Framework: Next.js 14 (App Router)
- Database: PostgreSQL 16 via Prisma
- Testing: Vitest + React Testing Library
## Architecture
- Server Components by default; use `"use client"` only when interactivity requires it
- API routes in /app/api; no business logic in route handlers
- Business logic in /lib/services; each service is a plain function module
## Conventions
- File naming: kebab-case for files, PascalCase for React components
- Error handling: wrap service calls in Result<T, E> pattern (see lib/result.ts)
- Never expose raw database IDs in API responses; use UUIDs
## Path-Scoped Modules
@src/api/CLAUDE-api.md
@src/components/CLAUDE-components.md
@prisma/CLAUDE-db.mdThe Goldilocks problem: altitude
Two failure modes appear consistently in production CLAUDE.md files:
Too vague: "Write clean code," "Follow best practices," "Keep functions small." These instructions pass through the model without changing behavior. The model already has a concept of "clean code" that predates your instruction, and it defaults to that concept, which may not match what your project needs. Aspirational rules are ignored.
Too granular: "Use 2-space indentation," "Add a blank line after import blocks," "Prefix private methods with underscore." These are linter rules, not cognitive decisions. They belong in .eslintrc, .editorconfig, or prettier.config.js, enforced deterministically by tools, not probabilistically by an LLM. Putting them in CLAUDE.md wastes context budget and produces unreliable enforcement.
The productive altitude: Capture decisions the model would make differently without the instruction. The test is: "Would Claude, with no project context, reasonably do something different here?" If yes, the rule belongs in CLAUDE.md. If the answer is aspirational, cut it. If a linter enforces it, cut it.
| Altitude | Example | Verdict |
|---|---|---|
| Too vague | "Write clean code" | Cut: model ignores, no behavior change |
| Too vague | "Follow best practices for security" | Cut: replace with specific constraints |
| Productive | "Never expose raw database IDs in API responses; use UUIDs" | Keep: specific, model would default otherwise |
| Productive | "Use the Result<T, E> pattern for service functions, not try/catch" | Keep: specific, overrides a common default |
| Too granular | "Use 2-space indentation" | Cut: delegate to Prettier |
| Too granular | "Add JSDoc comments to every function" | Cut: delegate to a lint rule |
The architecture choices, quality standards, and explicit "what not to do and why" rules are the productive altitude. The aspirational and the mechanical are noise.
Session Configuration
Mechanism: Inline instructions, /add-dir, or system prompt flags for the current session.
What belongs here:
- One-off task constraints ("For this refactor, do not change the public API surface")
- Experiment parameters ("Use the new error format I'm testing in this file")
- Debug constraints ("Log every tool call for this session")
- Temporary overrides of project conventions
Session instructions are not persisted. They evaporate when the session ends. Any instruction that you find yourself repeating across sessions belongs in the project config, not the session layer.
Decision Tree: Where Does This Rule Go?
Is this rule relevant to every project I work on?
├── Yes → Global CLAUDE.md
└── No ↓
Is this rule relevant to specific files or subsystems?
├── Yes → Path-scoped module (e.g., src/api/CLAUDE-api.md)
└── No ↓
Is this rule relevant to the whole project?
├── Yes → Project CLAUDE.md (root)
└── No ↓
Does this rule apply only to the current task or session?
├── Yes → Inline session instruction
└── No → Revisit: is it really a rule, or just a one-time preference?Import Chain and Override Semantics
The import chain flows: global → project root → path-scoped modules → session.
When conflicts exist:
- More specific overrides less specific (path-scoped beats root, root beats global)
- Later-declared beats earlier-declared at the same level
- Session instructions override all persistent config
Practical example: Your global config says "use two-space indentation." Your project config says "use four-space indentation for Python." Your session says "match the existing file style." The session instruction wins for this session, with four-space default for Python files, two-space for everything else.
Document your overrides explicitly. An undocumented override that contradicts a parent rule creates confusion during audits.