Every developer I've shown this to has the same reaction: "Why didn't I know about this sooner?"
Claude Code supports a project-level configuration file called CLAUDE.md. Drop it in your repo root and Claude reads it at the start of every session — before it writes a single line of code or answers a single question. It's the closest thing to onboarding a new developer to your project in under 30 seconds.
Here's what that means in practice.
The Problem It Solves
Without CLAUDE.md, you repeat yourself constantly:
- "Don't add comments unless the why is genuinely non-obvious."
- "We use pnpm, not npm."
- "Never import from
../../../utils— use the path alias@/utils." - "This service uses Postgres transactions for everything — don't add raw queries."
You say this on Monday. By Friday you're saying it again. By next month a new team member says it for you, because even they know you'll have to repeat it.
CLAUDE.md ends that cycle. Write it once, and every Claude session starts with full context.
A Minimal Working Example
Here's a trimmed version of what a real CLAUDE.md looks like for a TypeScript backend service:
# Project: Payments Service
## Tech stack
- Runtime: Node 20, TypeScript strict mode
- DB: Postgres via Drizzle ORM (never raw SQL)
- Package manager: pnpm (never npm or yarn)
- Tests: Vitest — run `pnpm test` before any commit
## Code style
- No comments unless the WHY is non-obvious (a workaround, a hidden constraint, a subtle invariant)
- Never explain WHAT the code does — identifiers do that
- Prefer editing existing files to creating new ones
- No feature flags for things you can just change
## Error handling
- Validate at system boundaries (user input, external APIs) only
- Trust internal code and framework guarantees
- Don't add fallbacks for scenarios that genuinely can't happen
## Commands
- `pnpm dev` — start local server
- `pnpm test` — run test suite
- `pnpm db:push` — apply schema changes
That's it. Less than 30 lines. Every Claude session in this repo now starts knowing not to use npm, not to add defensive error handling for impossible cases, and to run pnpm test before touching anything.
Where It Gets Interesting: Hooks
CLAUDE.md handles static rules well. Hooks handle behavioral rules — things you want enforced automatically when Claude takes an action.
Claude Code supports pre/post hooks in .claude/settings.json. Here's a hook that runs your linter every time Claude edits a file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "pnpm lint --fix \"${CLAUDE_TOOL_INPUT_FILE_PATH}\""
}
]
}
]
}
}
Now every file Claude touches gets linted automatically. You never come back to a PR with 40 lint errors.
Here's another — a pre-commit safety check that fires when Claude tries to run git commit:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node scripts/check-commit-safety.js \"${CLAUDE_TOOL_INPUT_COMMAND}\""
}
]
}
]
}
}
The check-commit-safety.js script can block anything: commits to main without a PR, commits with uncommitted .env files, commits that modify package-lock.json without also modifying package.json. If the script exits non-zero, Claude stops and tells you what went wrong.
The Hierarchy That Makes This Composable
Claude Code loads configuration files in a specific order:
-
~/.claude/CLAUDE.md— user-level (applies to all your projects) -
CLAUDE.mdin the repo root — project-level -
CLAUDE.mdin subdirectories — directory-level (only applies when Claude is working in that directory)
This means you can have:
- A user-level file for your personal preferences ("always use tabs, not spaces")
- A project-level file for the repo's standards
- A
backend/CLAUDE.mdwith API-specific rules and afrontend/CLAUDE.mdwith component-specific rules
They compose. The more specific file doesn't replace the less specific one — it extends it.
What to Put in It (and What Not To)
Good candidates for CLAUDE.md:
- Tech stack and version specifics (runtime, package manager, DB client)
- Architectural decisions that aren't obvious from the code (why you use X pattern, what's intentionally missing)
- Dev workflow commands (
pnpm test,pnpm build,pnpm db:push) - Code style rules that conflict with AI defaults (especially: no auto-commenting, no defensive fallbacks for internal code)
- Hard constraints (never edit
generated/, never runDROPin migrations, always stage before commit)
What doesn't belong:
- Things derivable from reading the code (current function signatures, import paths)
- Things already in your linter/formatter config (let tooling enforce those)
- General advice Claude already follows (it doesn't need to be told "write readable code")
The goal is context that's genuinely non-obvious from reading the codebase. A CLAUDE.md that's 500 lines long is a CLAUDE.md that nobody maintains and Claude skims.
One More Pattern: The Validation Hook
Here's the pattern I've seen make the biggest difference in team usage. Add a PostToolUse hook that runs your test suite after Claude modifies source files:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash -c 'if [[ \"${CLAUDE_TOOL_INPUT_FILE_PATH}\" =~ src/ ]]; then pnpm test --run 2>&1 | tail -20; fi'"
}
]
}
]
}
}
Claude edits a file in src/. Tests run. If they fail, Claude sees the output immediately and fixes the regression before moving on. No more "Claude said it was done but the tests are broken."
The 80/20 Version
If you do nothing else:
- Create
CLAUDE.mdin your project root with your stack, your commands, and your two or three most important constraints. - Create
.claude/settings.jsonwith aPostToolUsehook running your linter on every edit.
That's 20 minutes of setup. The payoff is that Claude stops violating the same rules it violated last week, and you stop repeating yourself.
I've put together a pack of 30+ CLAUDE.md starters, hook templates, and slash command definitions covering the patterns above — organized by project type (TypeScript backend, Python service, fullstack Next.js, data pipeline). It's a one-time download at cooa.gumroad.com/l/dlsaxy if you want to skip the "figure it out from scratch" phase.
What are you putting in your CLAUDE.md? Drop it in the comments — I'm collecting real examples.
Related reading: If you are using AI to generate production code, the verification step matters more than most engineers realize. The One-Line Fix That Took 24 Hours to Find is a case study in what AI-generated and human-written code alike can miss: a missing volatile qualifier that silently corrupted data across 3,000 boot cycles before anyone suspected the compiler.
Top comments (0)