Skip to content

CLAUDE.md and AGENTS.md

CLAUDE.md is the highest-leverage file in your repository: Claude Code loads it into context at the start of every session, before any prompt you type. It is the one channel where a sentence you write once gets applied thousands of times. That leverage cuts both ways — every stale or vague line also gets applied thousands of times, and every unnecessary line taxes the context budget of every task, including the tasks it's irrelevant to.

AGENTS.md is the cross-tool standard for the same idea, read by Codex, Cursor, Copilot, and others. The content contract is identical. If you run multiple tools, maintain AGENTS.md as the source and make CLAUDE.md a pointer (or symlink) — one file to keep true. Claude Code also reads AGENTS.md directly when no CLAUDE.md exists.

The mental model

CLAUDE.md is not documentation. It is standing orders for a competent engineer with total amnesia — the things you would say in the first five minutes of every onboarding, if you had to onboard the same person every morning forever. That framing decides almost every inclusion question:

  • Would you say it to a sharp new hire on day one? → In.
  • Could they figure it out in 30 seconds by reading the code? → Out.
  • Does a linter already enforce it? → Out (the linter is the enforcement; the file line is dead weight).
  • Is it a hard constraint whose violation is expensive or invisible? → In, near the top.

What belongs

CategoryExample lines
Build / test / lint commandspnpm test -- --run (vitest, not jest — npm test hangs waiting for watch mode)
Non-obvious conventionsAll money amounts are integer cents. snake_case in the API, camelCase internally — mapped in src/api/serialize.ts.
Hard constraintsNever edit src/generated/** — regenerate with pnpm codegen. No raw SQL in src/payments/ (see ADR-0002).
Environment gotchasTests need Docker running (Postgres testcontainer). direnv loads .env.local; without it, auth tests fail with JWT_SECRET undefined.
Pointers to deeper docsArchitecture: docs/ARCHITECTURE.md. Specs: docs/specs/. Before adding a utility, check the capabilities index below.
Capabilities indexWe already have: retry (src/lib/retry.ts), pagination (src/lib/paginate.ts), currency formatting (src/lib/money.ts).
Workflow rulesUpdate TASKS.md and CHANGELOG.md in the same PR. Run pnpm typecheck before declaring done.

The unifying property: each line is non-derivable (the agent cannot infer it from code), load-bearing (violating it causes real damage), or a pointer (cheap in context, expands on demand).

What does not belong

  • Anything derivable from code. "We use Express" — the agent sees package.json in the first ten seconds. Framework lists, directory tours, dependency inventories: all dead weight.
  • Style minutiae a linter enforces. Quote style, import ordering, line length. If it's not linted, either lint it or accept the variance — a CLAUDE.md line is the weakest of the three options.
  • Essays. Explanations of why the architecture is the way it is belong in docs/ARCHITECTURE.md and ADRs, with a one-line pointer here. CLAUDE.md states rules; other docs argue for them.
  • Aspirations. "We value clean, maintainable code" instructs nothing. If you can't name the concrete behavior change, cut the line.
  • Task-specific context. Details about the feature you're building this week belong in the spec, not in the file every unrelated session loads. See Prompts vs Specs.

The hierarchy and how Claude Code merges it

Claude Code composes instructions from three levels, all loaded together — more specific levels add to (and in conflicts, effectively override) broader ones:

  • User level (~/.claude/CLAUDE.md): your personal preferences across every project — "prefer rg over grep", "never git push --force". Not shared with the team; never put project facts here.
  • Repo root: team-shared, versioned, reviewed in PRs. The main event.
  • Subdirectory files: loaded when the agent works in that subtree. Carry only the delta from root — the billing package's stricter rules, its different test runner — never repeated root content. Repetition here is how contradictions are born: the root file gets updated, the copy doesn't, and the agent now holds both versions.

Instructions in the hierarchy are strong defaults, not physics — a direct prompt can override them, and a bloated file dilutes them. Precedence mechanics are covered in Context and Instruction Hierarchy.

Keep it under ~60 lines

Compliance with instruction files degrades as they grow: in a 40-line file every rule is salient; in a 300-line file, rules compete with each other and with the actual task for attention, and the agent effectively samples. Worse, long files hide contradictions — nobody rereads 300 lines, so line 12 says "tests colocated with source" while line 240 says "tests in tests/", and the agent picks one per session.

~60 lines is the working budget for the root file. The mechanism that keeps you under it is ruthless pointing: CLAUDE.md holds rules and pointers; everything with a paragraph of nuance gets its own file in docs/.

Weak vs strong

Weak — the 200-line onboarding essay (abridged; the real one goes on for pages):

markdown
# Project Overview

Welcome! This is our invoicing platform, built with Node.js and Express.
We chose Express in 2021 because the team was familiar with it...

## Tech Stack
- Node.js 20, Express 4, PostgreSQL 15, Redis for caching...
[14 more dependency bullets the agent can read from package.json]

## Directory Structure
- src/routes — the API routes
- src/models — the database models
[12 more self-evident bullets]

## Coding Standards
We believe in clean code. Use meaningful variable names. Functions should
be small and do one thing. Prefer single quotes. Always handle errors...

## Git Workflow
Create a feature branch, open a PR, get a review, squash-merge...
[standard flow every tool already follows]

Every line is true and almost none of it changes agent behavior. The tech stack is derivable, the directory tour is derivable, "clean code" instructs nothing, quote style is Prettier's job. The two facts that would have prevented bugs — cents-not-floats, the codegen directory — aren't in it.

Strong — 40 lines, every one load-bearing:

markdown
# invoicing-api

Node 20 + Express + Postgres invoicing service. Architecture: docs/ARCHITECTURE.md.
Feature specs: docs/specs/. Decisions: docs/decisions/ (read before proposing redesigns).

## Commands
- Dev server: `pnpm dev` (needs Docker up — Postgres testcontainer)
- Tests: `pnpm test -- --run` (vitest; plain `pnpm test` hangs in watch mode)
- Typecheck + lint: `pnpm check` — run before declaring any task done
- DB migration: `pnpm db:migrate:new <name>` — never edit applied migrations

## Hard constraints
- Money is ALWAYS integer cents (`amountCents: number`). Never floats, never strings.
- Never edit `src/generated/**` — regenerate with `pnpm codegen`.
- No raw SQL in `src/payments/` (ADR-0002); use the query builders in `src/db/`.
- API is versioned: breaking changes go in `/v2/`, never mutate `/v1/` responses.

## Conventions the code won't tell you
- API JSON is `snake_case`, internal code `camelCase` — mapping lives in `src/api/serialize.ts` only.
- Timestamps: UTC ISO-8601 strings in the API, `Date` internally, `timestamptz` in Postgres.
- Feature flags: `src/flags.ts` — check there before conditionally shipping anything.

## We already have (search before writing new)
- retry with backoff: `src/lib/retry.ts`
- pagination: `src/lib/paginate.ts`
- money formatting: `src/lib/money.ts`
- request-scoped logger: `src/lib/logger.ts` (use this, never `console.log`)

## Workflow
- Update `TASKS.md` status and `CHANGELOG.md` in the same PR as the change.
- If a spec in `docs/specs/` conflicts with the code, flag it — don't silently pick one.

The delta: the strong file contains only what the agent cannot derive and must not violate, plus pointers that expand on demand. It reads in seconds, fits any context budget, and every line has a failure mode attached to its absence.

The maintenance loop

Two rules keep the file alive:

Said it twice in chat → add it here. The first time you correct the agent ("no, we use vitest"), that's conversation. The second time — in another session — that's a missing CLAUDE.md line. Corrections in chat die with the session; the file is where they compound. The lazy version works well: tell the agent itself, Add to CLAUDE.md: tests use vitest, run with pnpm test -- --run. (Claude Code's # shortcut does exactly this.)

Update it as part of PRs. When a PR introduces a new command, invariant, or utility worth indexing, the CLAUDE.md line ships in the same PR — reviewed by the same reviewer, versioned with the change that made it true. This is what keeps the file from decaying into the weak example above; the same discipline that governs keeping specs alive.

Maintenance checklist

Run quarterly, or whenever the file crosses ~80 lines:

markdown
- [ ] Every command in the file still runs, verbatim, on a fresh clone
- [ ] No line is derivable from code in under a minute — delete tech-stack tours
- [ ] No line duplicates what a linter/formatter/CI gate already enforces
- [ ] Every pointer target (docs/, specs/, ADRs) still exists and is current
- [ ] Capabilities index matches reality — each listed util exists at that path
- [ ] No contradictions between root and per-package files (packages carry deltas only)
- [ ] Every rule traces to a real incident or repeated correction — cut speculative rules
- [ ] File is under ~60 lines (root); if not, promote sections to docs/ and point
- [ ] Recent chat corrections you've made twice are captured as lines

A field manual for AI-native software engineering.