· 6 min read ·

CLAUDE.md Is Configuration, Not Documentation

Source: hackernews

The CLAUDE.md file gets added to most projects during initial Claude Code setup, filled with roughly accurate notes about project structure, and then promptly forgotten. This is roughly equivalent to writing a .eslintrc, noticing the linter does not immediately crash, and assuming you are done.

The anatomy article that recently made the rounds on HackerNews covers the full .claude/ directory structure well. What the structural overview does not address is a more practical question: what separates a CLAUDE.md that actually changes how Claude Code behaves from one that is essentially noise in the context window?

What CLAUDE.md Actually Is

The file is injected into every Claude Code session as a prefix to the model context, before any conversation history or code. Unlike RAG-style retrieval where relevant documents are pulled in on demand, CLAUDE.md is always present. This reliability is a deliberate design choice: you never have to wonder whether the model has seen your project conventions.

But that reliability has a cost. The content competes for context space with everything else in your session: the conversation history, the files being read, the code being generated. Claude’s current context window handles 200k tokens, but a bloated CLAUDE.md still displaces context that could be used for actual code. Brevity matters more than completeness.

The HackerNews discussion that followed the anatomy article surfaced the same underlying question repeatedly: what should actually go in this file? Most answers were structural (“I put my build commands and architecture overview”), fewer were analytical about why certain content helps and certain content does not.

The Documentation Trap

Most CLAUDE.md files read like README files: here is what the project does, here is how the architecture is organized, here are the main dependencies. This mirrors how you would brief a new human developer, and that is exactly the wrong frame.

The model can read your code. It can traverse directory trees, read file contents, examine imports, and infer most of what a README would tell it. Writing a paragraph explaining that services live in src/services/ and commands live in src/commands/ is low-leverage. The model can see the tree.

What the model cannot easily infer:

  • Which npm scripts you actually use in practice (npm run dev vs npx ts-node src/index.ts vs some custom build script)
  • Which version of a library’s API you are targeting when major versions have breaking changes
  • Hard constraints that are not encoded in the code itself (never commit directly to master, always use database transactions for writes)
  • Non-obvious project decisions (this codebase intentionally does not use X even though X is the common choice)

The working principle is: write what the model cannot read from the code, not what explains the code.

What Gets Used

Build and run commands are the highest-leverage content you can put in CLAUDE.md. Claude Code frequently gets this wrong on first encounter with an unfamiliar project, and getting it wrong means failing to verify that code changes actually build or pass tests. Specifying exact commands removes an entire class of mistakes:

# Commands
- Build: `npm run build` (outputs to dist/)
- Test: `npm test` (runs vitest, not jest)
- Dev: `npm run dev` (uses ts-node, no rebuild needed)
- Lint: `npm run lint` (--fix flag will auto-correct)

Technology-specific API patterns matter acutely when you are on a major version boundary. Discord.js v14 changed essentially the entire interaction model from v13. Without a note in CLAUDE.md, the model will generate valid-looking v13 code that fails at runtime on a v14 project. The same applies to any library that made significant API changes between major versions: the model’s training data contains both versions, and without a constraint, it has no reliable signal about which you are using.

Hard constraints, stated explicitly, persist across sessions. “Never commit directly to master” in CLAUDE.md is more reliable than repeating it in conversation, because it is present at the start of every session. The same applies to “always use Result types, never throw exceptions” or “all database writes require a transaction.” These are team agreements that are difficult to enforce in code and straightforward to state as behavioral constraints.

The Hierarchy in Practice

Claude Code reads CLAUDE.md files at three levels and concatenates them in order:

  1. ~/.claude/CLAUDE.md — global, applies to every project on your machine
  2. <project>/CLAUDE.md — project-level context
  3. <subdir>/CLAUDE.md — loaded when the agent operates inside that subdirectory

The global file is where personal working style belongs. Response verbosity, code style preferences that apply across all your work, standing instructions about how you prefer explanations structured. This file is yours and no one else’s; it should not be in a project repo.

The project root file is the thirty-second orientation: what this project is, which commands to run, what hard constraints apply. A new contributor should be able to read it and have a functional mental model of the project’s operational requirements.

Subdirectory files are the most underused tier. In a monorepo, packages/frontend/CLAUDE.md can specify the component library and React version constraints while packages/api/CLAUDE.md specifies the ORM patterns and schema conventions. The model loads only the files relevant to where it is currently working, so context stays focused.

The @import Pattern

You can reference other files from within CLAUDE.md:

# API Conventions
@./docs/api-conventions.md

# Database Schema
@./prisma/schema.prisma

This is valuable when documentation already exists in a specific format. API conventions written for human contributors can be pulled directly into context without duplication. Schema files are particularly useful: including the actual Prisma schema means the model knows the exact column names, types, and relations without needing to look them up.

The caveat is context cost. A 3,000-line schema import is a different decision than a focused 150-line conventions document. Large imports justify themselves when the model genuinely needs that context in every session. For context that is only relevant occasionally, letting the model read the file on demand is often more efficient.

Treating It as Configuration

The framing that produces better results is “configuration” rather than “documentation.” ESLint config gets updated when you add new rules. Prettier config gets updated when formatting conventions change. CLAUDE.md should update on the same cadence.

In practice: when you add a package with a non-obvious API, add a note. When the team agrees on a new convention, write it down. When the build system changes, update the commands section. When you notice the model repeatedly making the same mistake, add a constraint that addresses it. That last one is diagnostic feedback: a repeated mistake is evidence that something the model needs is not in the file.

Some teams add a CLAUDE.md check to their pull request template: “does this change require updating CLAUDE.md?” It sounds like overhead, and it is, but it costs thirty seconds and pays off when it prevents recurring incorrect behavior in future sessions.

The file is re-read on every session start. Changes take effect immediately with no restart or cache flush. The feedback loop between writing a constraint and seeing it influence behavior is tight, which makes iterating on the file relatively fast compared to other configuration systems.

The Relationship to the Rest of the Folder

The remainder of the .claude/ directory — settings.json permissions, hooks, slash commands, MCP server definitions — handles behavioral guarantees. CLAUDE.md handles context and reasoning. The two are complementary but serve different purposes.

A CLAUDE.md that instructs the model to “always run tests after making changes” is advice the model can reason around if it judges the situation differently. A PostToolUse hook that runs the test suite after every file write is guaranteed execution regardless of what the model decides. For things that need to always happen, hooks are the right mechanism. For things that need to inform reasoning and shape decisions, CLAUDE.md is the right mechanism.

The folder’s design assumes you will use both. A project that has only CLAUDE.md has good context injection but no behavioral guarantees. A project that has only hooks and permissions has guardrails but a model operating without project-specific knowledge. The combination is where the leverage is.

Was this interesting?