Appearance
Writing a Great CLAUDE.md / AGENTS.md
The first thing almost everyone does with a coding agent is drop a Markdown file into the repo root: Claude Code reads CLAUDE.md; Codex, Jules, Cursor, Amp, and a whole crowd of other tools read AGENTS.md; Aider and Gemini CLI can be pointed at the same file through configuration. The file looks trivial — plain text, no schema, no validation — but it's the best bang-for-buck lever you have over agent behavior: written well, the agent shows up to every session like a new hire who's already been on the job three weeks; written badly, it's either ignored outright or drowns the rules that actually matter in noise.
The Claude Code case study already covers what this mechanism is (load order, lazy loading, and the contrast with hooks). This article answers a single question: how do you write this file well? Conclusion first: treat it like code — commit in small steps, refactor regularly, and deleting should demand a stronger reason than adding.
What it really is: a persistent prompt layer, not configuration
First, let's fix a widespread misconception. The .md in the name, the repo-root location, the fact that it's versioned — these signs make people assume CLAUDE.md is a configuration file like .eslintrc. It isn't.
The official documentation is explicit: the contents of CLAUDE.md are injected at session start as a user message, into the context after the system prompt; the model reads it and tries to comply, but nothing enforces it. It's context, not configuration ("context, not enforced configuration"). If you need to actually block an action — say, forbid writing to the migrations/ directory — the right tool is a PreToolUse hook or a permission rule. Those speak in exit codes and never pass through the model's judgment.
So the precise framing for this kind of file is: a persistent prompt layer. It travels the same channel as the instructions you type into the chat box and obeys the same probabilistic logic of "the model might not listen" — the only difference is that it's present in every session. Nearly every writing principle in the rest of this article falls straight out of that framing:
- Because it rides the prompt channel, how you phrase things affects compliance — vague, verbose, self-contradicting instructions get weighed arbitrarily;
- Because it's present every session, every line is a standing cost — it pays a token tax on every turn and dilutes attention;
- Because nothing enforces it, safety red lines can't live only here — hard constraints belong in hooks and the permission layer.
One corollary
"I wrote 'no X' in CLAUDE.md and it still did X" is not a bug; it's the mechanism behaving normally. Debug in this order: first run /context to confirm the file actually loaded, then make the instruction more specific, and finally — if this is a zero-exception hard requirement — either demote it from CLAUDE.md to a plain reminder or promote it to a hook.
What to write, and what to leave out
Anthropic's official best practices provide a blunt trade-off table; this section builds on it. The litmus test fits in one sentence: this file should hold only what the model can't infer from the code but needs to know in every session.
Four things worth writing:
- Command cheatsheet: the exact commands to build, test, lint, and run a local dev server — especially the non-standard ones (
pnpm test --filter,make dev-docker). If the model can't guess it, it earns a line. - Project conventions: code style that differs from the language's defaults ("use ES modules, not CommonJS"), directory boundaries ("API handlers live in
src/api/handlers/"), commit and branch discipline. - Boundaries and no-go zones: directories that must never be touched, environments where certain commands must never run, what has to happen before a deploy. Attach a reason or an alternative path to every prohibition — compliance is markedly higher than with a bare "don't."
- Workflow preferences: which check to run after changing code, unit tests before the full suite, whether to ask clarifying questions first or propose a solution first when requirements are ambiguous.
Four things to leave out:
- Tutorials and explanations. CLAUDE.md is not onboarding material. The model doesn't need to re-read "our architecture uses the CQRS pattern because…" every session; when it needs the details, it will read the code.
- Obvious platitudes. "Write clean code." "Handle errors well." "Keep files organized." None of these has a verifiable criterion, so the model can't act on them — they only dilute the weight of every other rule. The official contrast is blunt: "Use 2-space indentation" works; "Format code properly" doesn't.
- What the model can infer on its own. Directory layout, dependency lists, the tech stack — all of it is sitting in
lsandpackage.json. The official/doctorpruning check cuts exactly this kind of content, keeping the landmines, the reasons, and the conventions that deviate from defaults. - Facts that go stale. Version numbers, current owners, this week's workaround. A stale fact is worse than no fact: the model will solemnly obey a rule that no longer holds.
The one-line test
The official best practices offer a minimal pruning test: for each line, ask "if I deleted this line, would Claude make a mistake?" If not, delete it. The criteria for adding work in reverse, and they're just as simple: the same mistake has happened twice, code review caught something the model should have known, or you've typed the same correction twice — only then does the line earn its place in the file.
Length and signal-to-noise: why shorter is more effective
The official memory documentation sets an explicit length target: keep each CLAUDE.md file under 200 lines; longer files consume more context and lower compliance. That's not a throwaway suggestion — two mechanisms sit behind it:
The first is the token tax. CLAUDE.md enters the context in full at every session start, and from then on it keeps participating in billing and attention allocation alongside every turn of the conversation. An 800-line file means paying a fixed cost before any real work begins, and that cost compounds with every turn.
The second is attention dilution. This is the deadlier one. The more rules there are, the lower the odds that any single rule gets followed — the official best practices warn outright: "if your CLAUDE.md is too long, Claude will ignore half of it because the important rules get drowned out by noise." The telltale symptom: you wrote the prohibition and the model violates it anyway — not because it's disobedient, but because that rule carries no "seen" weight inside three hundred lines of text. This is exactly what context engineering keeps emphasizing: a long context is not an effective context; the volume of information you inject and instruction-following are not positively correlated — past a certain point, the correlation turns negative.
Two practical takeaways worth memorizing:
- Want to add weight with
IMPORTANTorYOU MUST? Fine — but ration it. The official docs acknowledge that emphasis improves compliance — but emphasis is inflation; when everything is capitalized, nothing is. - Splitting doesn't cut the cost. Files pulled in via
@importare also loaded in full at startup; splitting into ten files just makes maintenance easier for humans — not a single byte leaves the context. The real way to cut cost is path-triggered loading (the layered organization below), so a rule enters the context only when a relevant file gets touched.
Layered organization: closest wins, load on demand
When a single file can't hold every instruction, the right move is not to write longer — it's to layer. As of mid-2026, Claude Code's layering looks like this (the further down, the more specific and the later it lands in context):
text
┌──────────────────────────────────────────────────────────────────┐
│ Organization managed policy (/etc/claude-code/CLAUDE.md, etc.) │ ← pushed by IT, cannot be excluded
│ User ~/.claude/CLAUDE.md + ~/.claude/rules/ │ ← personal preferences, apply to every project
│ Project ./CLAUDE.md or ./.claude/CLAUDE.md │ ← committed to git, shared with the team
│ Local ./CLAUDE.local.md │ ← gitignored, personal only
│ Directory CLAUDE.md / .claude/rules/ in subdirs (with paths) │ ← lazy loading: injected only when the model
└──────────────────────────────────────────────────────────────────┘ reads a file in that directoryLoading walks from the filesystem root toward the working directory, and the files it finds are concatenated, not overridden — the closer a file sits to the launch point, the later its instructions appear. Two direct consequences for how you write:
- Place rules where they apply: a convention that only affects
src/api/belongs insrc/api/CLAUDE.mdor in a rule withpaths: ["src/api/**/*.ts"]— don't pollute the root file. A subdirectory file enters the context only when the model actually reads a file in that directory — you don't pay tokens for instructions you never use. - Make conflicts explicit. When rules from different layers collide, the model will arbitrarily pick one to follow. The official advice is to audit the layer files periodically and eliminate contradictions; rather than hoping the model guesses right, write it down in the file: "when X and Y conflict, X wins."
The @path import syntax solves a different problem: a single source of truth. A README, package.json, or the team's git workflow doc can be pulled in with @ (relative paths resolve against the file containing the import, recursion capped at four levels), so the same convention never exists as two copies quietly rotting apart. For personal configuration that spans worktrees, @~/.claude/my-project-instructions.md works; the first time an import points outside the repository you'll get a confirmation dialog — a safety gate against shared projects committed by someone else.
Counterexample: a typical bad CLAUDE.md
You can find files like this in the wild every day (stitched together from common failure patterns). Look at it first, then the rewrite:
markdown
# About This Project
Welcome to our project! This is a modern full-stack application built on
React 17 + Node.js, dedicated to delivering an outstanding experience to
tens of millions of users.
## IMPORTANT NOTES!!!
- You MUST write clean code!!
- All code must have comprehensive error handling
- Remember to write comments — comments are a programmer's virtue
- IMPORTANT: keep the code elegant
- Never write bad code
## Architecture
Our system uses a microservices architecture. In Q3 2023 we completed the
migration from a monolith to microservices, led by Zhang… (40 lines of
history omitted here)
## Notes
- Be careful with anything database-related
- Write good testsIt commits nearly every mistake available: a tutorial-style opening (the model doesn't need a welcome); facts that will go stale (the React version, personnel info); platitudes with no criterion ("write clean code"); hollow warnings ("be careful" — how, exactly?); emphasis inflation (IMPORTANT everywhere is IMPORTANT nowhere); and long stretches of task-irrelevant history. The one piece of information that might work — if there is any — has already drowned.
And the rewrite:
markdown
# Commands
- Build: `pnpm build`; single test: `pnpm vitest run -t "<test name>"`
(don't run the full suite, it's too slow)
- Local environment: `make dev` (requires `docker compose up db redis` first)
# Conventions
- ES modules (import/export); CommonJS require is banned
- API handlers go in `src/api/handlers/` and return the standard error shape
(see `src/api/errors.ts`)
- Commit messages follow Conventional Commits
# No-go zones
- Don't edit merged migration files under `migrations/`; create a new
migration instead
- Never put secrets in code; for local debugging use `.env.local` (gitignored)
# Workflow
- `pnpm typecheck` must pass after every change — no calling it "done" while
it's red
- When changing files under `src/api/`, update `docs/openapi.yaml` in the
same changeSet the two versions side by side: every line passes the "would the model slip up without it" test; every rule is verifiable (commands that run, paths that check out, checks with exit codes); every no-go zone comes with an alternative path; and the length drops from over a screenful to twenty lines.
Team sharing and version control
The project-level CLAUDE.md should be committed to git — that's the essential difference between it and personal notes: it's a shared team asset that evolves alongside the codebase. The official best practices put it as "treat it like code" — review it when something goes wrong, prune it on a schedule, and after each change watch whether agent behavior actually shifts. From there, a few standard plays for team scenarios:
- Multiple people, multiple tools: AGENTS.md is currently the only open cross-tool convention (next section). Put shared conventions in AGENTS.md and tool-specific content in each tool's own file.
- Monorepos: each subproject gets its own nested file; other teams' CLAUDE.md files will get swept in by the ancestor-directory lookup, so exclude them with
claudeMdExcludesand keep instructions from bleeding across team lines. - Personal preferences stay out of the shared file: sandbox URLs, personal test fixtures, and the like go into
CLAUDE.local.md, added to.gitignore. - Cold start: running
/initin a session analyzes the codebase and generates an initial file (if one already exists, it suggests improvements instead of overwriting); as of mid-2026,/doctorcan also propose pruning for a CLAUDE.md that's already in the repo — cutting what the model could infer on its own, keeping the landmines and conventions. The generated file is only a starting point; the value comes from the incremental upkeep that follows each "it made this mistake again."
AGENTS.md: the cross-tool common layer
CLAUDE.md is Claude Code's private convention; AGENTS.md is the open format launched in 2025 by OpenAI Codex, Amp, Google Jules, Cursor, Factory, and others, positioned as "a README for agents" — a predictable home for build commands, test instructions, and code conventions. As of mid-2026 it has been adopted by more than 60,000 open source projects and is stewarded by the Agentic AI Foundation under the Linux Foundation.
A few mechanical differences worth knowing:
- Plain Markdown, no schema, no required fields of any kind — the agent parses the text directly.
- Closest file wins: among nested AGENTS.md files, the one nearest the file being edited takes effect; the user's explicit instruction in the current turn outranks every file. The official agents.md site notes that OpenAI's main repository alone contains 88 nested AGENTS.md files.
- Claude Code doesn't read it natively. The standard play for two-tool teams: maintain one AGENTS.md as the shared layer, then have CLAUDE.md import it with
@AGENTS.md(Claude-specific instructions can follow below); when there's nothing to append, a symlinkln -s AGENTS.md CLAUDE.mdis enough. Aider takesread: AGENTS.mdin.aider.conf.yml, and Gemini CLI takes afileNamein its settings — all pointing at the same file.
Strategy advice: write universal conventions in AGENTS.md (aimed at all agents and future tools), and tool-specific behavior in each tool's private file. It's the same stance you'd take toward programming languages — bet on the open layer, isolate the private one.
Division of labor with skills and hooks
CLAUDE.md is not the only channel for injecting knowledge — before writing, confirm this piece of knowledge really belongs here. The division of labor among the three mechanisms, in one sentence: rule files handle the constraints that apply every session, skills handle the procedures activated on demand, and hooks handle the zero-exception guarantees.
| CLAUDE.md / rule files | Skill | Hook | |
|---|---|---|---|
| Nature | Advisory | Advisory | Deterministic |
| Injection timing | Resident from session start / path-triggered | Loaded only when the model judges it relevant | Not injected; scripts run on lifecycle events |
| Best for | Command cheatsheet, conventions, no-go zones | Multi-step procedures, release checklists, domain know-how | Run lint after every edit, forbid writes to a directory |
| Cost profile | A tax on every session | Zero cost while idle | Zero context cost |
The classic misuse is writing an eight-step release process into CLAUDE.md — relevant only at release time, yet taxing every session; the right home is a skill (see Skills). The reverse misuse is putting safety red lines only in CLAUDE.md — the model may not comply, and hard constraints must land in a hook or permission rules. The deciding test loops back to the start of this article: CLAUDE.md is a prompt layer, and a prompt layer's promise is "best effort," not "guaranteed."
A template you can adopt as-is
The principles above, compressed into a harness: a new project can start from it and grow under the discipline of "add a line only after the same mistake happens twice":
markdown
# <project name>
One sentence on what this project is and who it's for. (Just one — delete any more.)
# Commands
- Install dependencies: `<cmd>`
- Build: `<cmd>`; test: `<cmd>` (how to run a single test: `<cmd>`)
- Lint / typecheck: `<cmd>`
- Local dev server: `<cmd>` (prerequisites: <if any>)
# Conventions
- <Code style that deviates from the language default, one rule per line>
- <Directory boundaries: what must go where>
- <Commit / branch / PR discipline>
# No-go zones
- Don't <action>; when needed, take <alternative path> instead
- Don't touch <directory/file>; reason: <one sentence>
# Workflow
- A change is only done once <check command> passes
- <Preference when hitting a given task type: ask first / propose a plan first / write tests first>
# Shared conventions for other agents: see @AGENTS.md (if present)Once it's written, do three things: run /context to confirm it loads; count the lines (past 200, split into layers or cut); and commit it to git so the whole team keeps it alive.
One final meta-rule
This file's ideal length isn't written — it's pruned. The first draft is always too long — start using it, and every time you notice "the model would have done this anyway" or "this is out of date," delete a line. Whatever survives a quarter is what your project genuinely needs to tell the agent.
Further reading
- Claude Code case study — CLAUDE.md's loading mechanics and its contrast with hooks; the mechanism background for this article
- Context engineering — why "shorter is more effective": the full discussion of token budgets and attention dilution
- Skills and knowledge injection — the trade-offs among static injection, RAG, and skills, and how
.claude/rules/divides work with skills - Permissions and human–agent collaboration — how to implement the hard constraints a prompt layer can't guarantee
- Memory — how CLAUDE.md compares with auto memory and heavier memory architectures
- Design principles — principles that extend from this article to harness design as a whole
References
- Claude Code official docs: Memory — the 200-line length target, load order,
@import,.claude/rules/,claudeMdExcludes,/doctorpruning - Anthropic engineering blog: Claude Code Best Practices — the CLAUDE.md trade-off table, the "would deleting it cause a mistake" test, and the warning that bloated files get their rules ignored
- AGENTS.md official site — the open format's positioning, its launchers (OpenAI Codex / Amp / Jules / Cursor / Factory), Linux Foundation stewardship, and the closest-file-wins rule
- Claude Code official docs: Hooks — the other end of the "advisory vs. deterministic" division of labor