What Is CLAUDE.md and Why Does It Matter?
If you've been using Claude Code for more than a few sessions, you've probably noticed that it occasionally gives you advice that doesn't quite fit your project. It suggests a folder structure you'd never use, proposes a pattern that conflicts with your conventions, or asks clarifying questions you've already answered in a previous session. That friction adds up. CLAUDE.md exists to fix that.
CLAUDE.md is a plain Markdown file you place at the root of your project (or in your home directory as a global default). When Claude Code starts a session, it automatically reads this file and loads it as persistent context. Everything in it becomes part of how Claude understands your project from the very first message. It doesn't replace the conversation β it front-loads the context so you skip the setup phase entirely.
Think of it as a briefing document you'd hand to a new developer on day one. Except instead of a developer who needs a week to absorb it, Claude Code acts on it immediately. Stack, architecture decisions, naming conventions, scripts to run, things to never do β all of it available from message one. I've been using this pattern for about eight months across my .NET/Angular projects and it has fundamentally changed how much context I need to re-explain every session.
Why I Finally Got Serious About CLAUDE.md
The moment that pushed me to actually invest time in CLAUDE.md was a production incident I caused by trusting Claude's generic advice too quickly. I was working on a .NET 8 Web API project with a very specific repository pattern that diverges from the textbook version β our unit of work is scoped at the request level, not per-operation, and we use a custom IOperationResult<T> pattern instead of exceptions for expected failures. Claude kept generating code that threw exceptions on business-rule violations, which conflicted with everything else in the codebase. I kept correcting it. Session after session.
When I finally wrote a proper CLAUDE.md, that entire class of errors disappeared. I've since built CLAUDE.md files for every active project: the .NET API, a couple of Angular frontends, my n8n automation repo, and my homelab Ansible playbooks. Each one is tailored. Each one saves me ten to fifteen minutes of re-explanation per session, which adds up to hours per week.
What I'll walk through here is the structure I actually use, the patterns that work, and the mistakes I made early on so you don't have to.
The Anatomy of a Well-Written CLAUDE.md
There's no official schema. CLAUDE.md is just Markdown that Claude Code reads. But after months of iteration, I've settled on a structure with five core sections. Not every project needs all five, but these are the categories that eliminate the most friction:
- Project Overview β What this is, who it's for, what it does
- Tech Stack and Versions β Exact versions matter; Claude behaves differently for .NET 6 vs .NET 9
- Architecture and Conventions β The decisions that diverge from defaults
- Common Commands β Build, test, lint, run β the commands Claude should suggest when relevant
- Rules and Guardrails β Explicit "never do this" instructions
Keep it dense but scannable. Bullet points over paragraphs. Claude Code reads the whole file on session start, but when it needs to refer back to something mid-conversation, concise entries are easier to match against. I've also found that shorter CLAUDE.md files (under 300 lines) are more consistently respected than sprawling ones. If yours is getting long, split it into CLAUDE.md at the root plus .claude/ subdirectory files β Claude Code supports both.
One important distinction: CLAUDE.md is different from a project README. The README is for humans who need to understand the project. CLAUDE.md is specifically for Claude Code β you can be blunter, more prescriptive, and skip the explanations you'd normally include for a human audience. "Use IOperationResult, never throw" is a perfectly valid CLAUDE.md entry. You don't need to justify it. Claude will just do it.
Writing the Project Context Section
The project overview is the first thing Claude reads, so make the most important context land immediately. Here's the opening section of the CLAUDE.md I use for my primary .NET API project:
# Project: Procurement API
A .NET 9 Web API serving an Angular 19 frontend and internal integrations.
Multi-tenant SaaS. PostgreSQL via EF Core 9. Deployed on Azure Container Apps.
## Key Architecture Decisions
- Repository pattern with Unit of Work scoped per HTTP request (not per operation)
- Results pattern using `IOperationResult<T>` β NEVER throw exceptions for business rule failures
- CQRS via MediatR β all business logic lives in handlers, never in controllers
- Controllers are thin: validate input, call mediator, return result
- No AutoMapper β manual mapping only, in dedicated mapper classes under /Mapping/
## Tech Stack
- .NET 9 / C# 13
- Entity Framework Core 9 (code-first, PostgreSQL)
- MediatR 12
- FluentValidation 11
- Serilog (structured logging to Seq)
- xUnit + NSubstitute for tests
Notice what I'm doing: I'm not just listing the stack, I'm explaining the decisions that diverge from what Claude would generate by default. The results pattern line is the most important one in that file. Every time I start a session and ask Claude to generate a handler, it immediately uses IOperationResult<T> without me asking. That's the value β Claude has internalized the decision so I don't have to re-litigate it every session.
One pattern I've started using: I include a brief "Recent Changes" section in my project CLAUDE.md that I update whenever I make a significant architectural decision. Something like "As of 2026-09 we migrated from Hangfire to the .NET 9 native job scheduler." This gives Claude immediate context about why the codebase looks the way it does. The key discipline is to focus on decisions, not facts β Claude already knows what Entity Framework Core is; what it doesn't know is that our DbContext uses a custom interceptor for soft-delete logic. Those are the facts that change how Claude generates code.
Coding Standards That Claude Will Actually Follow
This is where most CLAUDE.md files I've seen online fall short. They list things like "follow C# naming conventions" β which tells Claude nothing it doesn't already know. What you want to document is the conventions that deviate from the language defaults, or the ones your team enforces that aren't universal.
Here's a real excerpt from my conventions section:
## C# Conventions
- Private fields: `_camelCase` (underscore prefix, always)
- Async methods: always suffix with `Async`, even on interface definitions
- Commands/Queries: name as `{Verb}{Entity}Command` or `{Verb}{Entity}Query`
- Handlers: always in same file as their command/query (not separate files)
- No `var` for primitive types β explicit type declaration only
- Exception: `var` is fine for LINQ result types and `new()` expressions
## Angular Conventions (frontend repo)
- Feature modules, not standalone components β we're pre-Angular 17 and not migrating yet
- State management via NgRx β never local component state for shared data
- API service names: `{Feature}ApiService` (not just `{Feature}Service`)
- Barrel exports (index.ts) for every feature folder
That last Angular note about feature modules saved me a massive argument with Claude. It kept suggesting standalone components because that's the modern Angular default. Once I added that line, it stopped β and when I asked it to generate a new feature, it scaffolded a proper feature module without me correcting it.
If you're getting started with Claude Code or want to see how it compares to other AI coding tools, I covered the .NET-specific angle in detail in my Claude Code vs Cursor vs GitHub Copilot for .NET post. CLAUDE.md is what tips the balance significantly in Claude Code's favor for complex projects.
One pattern I've started using recently: I include a brief "Recent Changes" section in my project CLAUDE.md that I update whenever I make a significant architectural decision. Something like "As of 2026-09 we migrated from Hangfire to the .NET 9 native job scheduler" with a one-line explanation. This gives Claude immediate context about why the codebase looks the way it does without me having to re-explain migrations mid-session.
The Commands Section β Underrated but Critical
When Claude Code runs commands on your machine (via the bash tool), it needs to know which commands are safe, which ones require confirmation, and what the correct invocations are for your project. Without a commands section, Claude will guess, and those guesses are sometimes wrong or suboptimal.
## Commands
### Build and Run
```bash
# API (from /src/Api/)
dotnet run --launch-profile Development
# Run all tests
dotnet test --filter "Category!=Integration" --no-build
# Integration tests (requires Docker Compose running)
dotnet test --filter "Category=Integration"
# Angular frontend (from /frontend/)
npm run start:dev
```
### Database
```bash
# Apply pending EF migrations
dotnet ef database update --project src/Infrastructure
# Add a migration (run from solution root)
dotnet ef migrations add {MigrationName} --project src/Infrastructure --startup-project src/Api
```
### Docker
```bash
# Start dependencies (Postgres, Seq, Redis)
docker compose -f docker-compose.dev.yml up -d
# Rebuild API image
docker compose build api
```
The reason this matters in practice: when I'm deep in a session and ask Claude to "run the tests," it runs exactly the right command without me specifying the filter. It knows integration tests need Docker running. It knows where to run EF migrations from. That's a small thing per session but a meaningful one across dozens of sessions per week.
I've also started annotating commands with brief inline comments explaining prerequisites. "Requires Docker Compose running" before the integration test command has saved me from sessions where Claude confidently ran integration tests in a bare environment and then spent five minutes debugging connection errors that were entirely predictable. A single comment line prevents that whole detour.
Rules and Guardrails β The "Never Do This" Section
This is my favorite section because it's the most direct. I list things I've had to correct Claude on at least twice. If I've corrected it twice, that means it's something Claude consistently does by default that conflicts with my conventions. Writing it into CLAUDE.md means I only correct it once.
## Rules β Always Follow These
- NEVER use `Console.WriteLine` β use `ILogger` injection only
- NEVER add `using` statements to controller constructors directly β inject via primary constructors (.NET 8+)
- NEVER suggest switching from EF Core to Dapper β that decision is made
- NEVER use `Task.Result` or `.GetAwaiter().GetResult()` β always await properly
- Do NOT create new projects or modify the .sln file without explicit instruction
- Do NOT install npm packages without confirming first
- Prefer `IReadOnlyList<T>` over `List<T>` for return types on read operations
- When generating tests, always use the Arrange/Act/Assert comment structure
That "never suggest switching from EF Core to Dapper" line is one I'm particularly proud of. Claude has strong opinions about when raw SQL via Dapper is "better" and would occasionally bring it up unprompted. That line killed that conversation permanently. It's not a discussion β the decision is made, move on.
I also covered Claude Code's hooks system for automating pre/post-command behavior in my Claude Code Hooks tutorial β combining hooks with a solid CLAUDE.md gives you an extremely tight development loop.
Global vs Project-Level CLAUDE.md
Claude Code supports two levels of CLAUDE.md: project-level (at the repo root) and global (at ~/.claude/CLAUDE.md). I use both. My global CLAUDE.md captures things that are true across all my projects:
# Global Context β Ricardo Gil
## About Me
Full-stack developer (.NET/Angular). I run Ollama locally on a Beelink SER5 with 32GB RAM.
I use n8n for automation workflows. Self-hosted stack on Proxmox.
## My Preferences (All Projects)
- I prefer verbose error messages over silent failures
- Always suggest the async/await pattern β never callbacks
- When I ask for a "quick" implementation, give me working code first, refactor suggestions after
- I use Claude Code daily β don't explain what it is or suggest alternatives
- I run Linux on my homelab and macOS on my dev machine
## Never Do (Any Project)
- Never suggest Windows-only solutions without a Linux equivalent
- Never use `any` in TypeScript β always explicit types
- Don't add JSDoc comments unless I ask β I find them noisy for internal code
The global file sets my baseline. The project file overrides and extends it. Together, they mean Claude Code hits the ground running on every project without me explaining who I am or how I work.
One thing I wish I'd done earlier: version-controlling my global CLAUDE.md. I have it in a private dotfiles repo now, which means I can track changes over time and restore earlier versions if an edit makes things worse. It's a small thing, but the global CLAUDE.md is something you'll refine constantly, and being able to diff it against last week's version is genuinely useful.
Advanced Patterns: MCP Hints and Subagent Context
If you're using custom MCP servers with Claude Code (I wrote a whole guide on building custom MCP servers), you can document them in CLAUDE.md too. Claude Code will know what tools are available and when to reach for them:
## Available MCP Tools
- `database-inspector`: Query the dev PostgreSQL directly. Use for exploring schema, checking data.
- `n8n-api`: Trigger and inspect n8n workflows. Available at localhost:5678.
- `seq-logs`: Search structured logs from the last 24h. Useful for debugging production-like issues.
Prefer MCP tools over running raw SQL or curl commands when the tool exists for the task.
This is genuinely powerful. Claude Code knows to reach for the database-inspector tool when I ask something like "what columns does the orders table have?" instead of trying to read the migrations folder. That's the kind of context that transforms Claude from a smart autocomplete into something closer to a colleague who knows the project.
If you haven't built a custom MCP server yet, the MCP hints section of CLAUDE.md is a good reason to start. Even a simple server that wraps one or two internal APIs is worth building if you're querying those APIs frequently through Claude Code. The combination of CLAUDE.md context hints and a custom MCP server eliminates almost all the repetitive setup that slows down AI-assisted development.
My Production CLAUDE.md Starting Template
Here's the skeleton I use when starting a new project. Fill in the blanks, delete sections that don't apply:
# Project: [Name]
[One sentence: what this is and what it does.]
## Tech Stack
- [Runtime and version]
- [Framework and version]
- [Database]
- [Key libraries]
## Architecture
- [Key pattern or decision #1]
- [Key pattern or decision #2]
- [Key pattern or decision #3]
## Conventions
- [Naming convention that deviates from default]
- [File structure decision]
- [Pattern preference]
## Commands
```bash
# [Action]
[command]
# [Action]
[command]
```
## Rules
- NEVER [thing Claude keeps doing wrong]
- NEVER [thing Claude keeps doing wrong]
- Prefer [X] over [Y] for [reason]
Start with this, run a few sessions, and add to it every time you find yourself correcting Claude on something for the second time. CLAUDE.md is a living document β mine accumulates a few lines per week.
One workflow I've settled into: at the end of a productive Claude Code session, I review the conversation for any corrections I made and add them to CLAUDE.md immediately. Takes about two minutes and means the next session starts with those corrections already baked in. It's a compounding improvement β the more you use Claude Code, the better your CLAUDE.md gets, and the better your CLAUDE.md gets, the more you get out of Claude Code.
Hardware That Pairs Well With This Workflow
Since I run Ollama locally for things Claude Code offloads to local inference (embedding generation, quick summarization tasks), the hardware matters. I've been running my setup on a GMKtec G3 N100 Mini PC (~$189) as a dedicated Ollama inference node β it handles smaller models (Llama 3.2 3B, nomic-embed-text) without breaking a sweat, and at that price it's hard to argue against dedicating a machine to it. For heavier local inference on larger models, you'll want to look at RAM first β I upgraded the memory in my Beelink with a Crucial 32GB DDR4 SODIMM kit (~$59), which made a significant difference running 7B and 13B models concurrently. And for the actual coding sessions, I've been on a Logitech MX Keys (~$99) for about two years β the tactile feedback is just right for long Claude Code sessions where you're reading output and typing follow-ups constantly.
The reason local inference matters for a CLAUDE.md-heavy workflow is that I often use embedding models to pre-process documentation before adding it to my CLAUDE.md context. Running nomic-embed-text locally via Ollama means I can compute semantic similarity between chunks cheaply and fast, then include only the most relevant context in my CLAUDE.md rather than dumping everything in and hoping Claude prioritizes well. It's an extra step, but for large codebases it makes the context file significantly more effective.
If you're not yet running Ollama locally, the Proxmox + Beelink setup I've described elsewhere on this site is still my recommended starting point for a dedicated homelab AI node. The mini PC form factor keeps power consumption low (the N100 idles at under 10W) while giving you enough headroom to run the models that actually matter for developer tasks.
What a Good CLAUDE.md Actually Buys You
I've tracked this informally across my projects. Before I had CLAUDE.md files, roughly 20-30% of my messages in any given session were corrections β Claude suggesting something that didn't fit my conventions, me redirecting it, Claude adjusting. With a mature CLAUDE.md, that drops to under 5%. The correction rate in the first ten messages of a session went from high to near-zero.
That compounds. If you use Claude Code for four or five hours a week (which is conservative if you're doing it seriously), and you're saving 20% of your message count on corrections, you're getting back meaningful time and token budget. More importantly, the sessions feel like working with someone who knows the project β not someone you're constantly onboarding.
The investment to write a good CLAUDE.md is maybe two hours for a complex project, thirty minutes for a simpler one. It pays back that investment in the first week. I'd put it in the same category as writing a good README or setting up CI β the kind of infrastructure work that feels optional until you realize how much worse everything is without it.
Need AI tools integrated into your dev workflow?
I build custom AI automation pipelines with Claude API, n8n, and local LLMs for development teams. Let's talk β