How to Write a CLAUDE.md File Your AI Agent Actually Uses
AI links open with a title + excerpt (these tools can't fetch the page themselves) — use "Copy full article" to paste the complete text for a fuller summary.
Introduction
This article shares how we write the CLAUDE.md file at the root of every repository.
The main one is about 16KB — around 2,500 words of architecture notes. No compiler reads it. No test covers it. And it is still the highest value file in the project.
The reason is not the one you would expect. It is not that it makes the AI agent clever. It is that writing documentation for an agent forces you to write down the reasoning you normally keep in your head.
The Problem
Give a coding agent a large repository and it has the same problem as a new team member. The code says what. It never says why.
Why is the Cosmos DB partition key /id, when that makes every document its own partition? Why does the contact form write to Firestore when the blog is in Cosmos DB? Why is that cookie's secure flag conditional instead of simply true?
With no answer, the agent does the reasonable thing. It guesses the intent from the code around it. And reasonable guessing is exactly how a codebase drifts. It "fixes" the conditional secure flag to always true, and local login quietly stops working.
A person does the same thing. Only slower, and they apologise after.
Four Things That Belong In It
Our file settled into this shape after a few weeks.
1. How to Actually Run the Project
Not npm install && npm start. The real steps, including the parts that are not obvious.
npm run install:all # frontend + api deps
npm run start:azurite # storage emulator (separate terminal)
npm start # SWA emulator + API + frontend
Also the ports — 4300 for Angular, 7080 for Functions, 4290 for the Static Web Apps emulator — and which one you open in the browser, because it is not the one the Angular CLI prints.
And the failure you will hit. If a port is reported as unavailable, a stale func.exe from the last run is usually still holding it.
netstat -ano | findstr <port>
taskkill /F /PID <pid> /T
That paragraph is there because we hit it many times. Writing it down turned a repeating ten minute problem into a lookup.
2. Decisions With the Reason Attached
This is the most important part, and the part most documentation skips.
The cookie's
secureflag is conditional onWEBSITE_INSTANCE_ID(an Azure-only env var) since local dev via the SWA CLI emulator is plain HTTP and a hardcodedsecure: truewould silently break local login.
See what that line does. It records the decision, the mechanism, and the failure it prevents. Anybody who sees that condition and thinks "this looks like a bug" now finds the answer before they change it. The word "silently" is doing real work — it warns you that the breakage will not announce itself.
Here is another one, about the auth code.
Protected handlers call
requireAuth(request)and return its 401 response directly if invalid — there is no Express-style middleware in the Azure Functions v4 model, so this check is repeated at the top of each protected handler instead.
That repeated line looks like careless code. It is a limit of the runtime. One sentence stops the next person from "cleaning it up" into a middleware that cannot exist.
3. Where the Trust Boundary Is
Security notes give the highest return, because a mistake here is expensive and the code alone never explains it.
contentMarkdownis trusted content, but not because it is inert import data anymore — the admin blog editor writes this field directly, so the trust boundary is "only the single authenticated admin can write it," not "never user input."
That is a note about a boundary that moved. The code still renders Markdown through bypassSecurityTrustHtml exactly as before. What changed is the justification. If the justification is not written down, the next feature that lets somebody else write that field will look harmless.
4. What Is Not Done Yet, On Purpose
None of these are used everywhere yet — they exist so new features have a ready-made, on-brand primitive instead of one-off CSS.
Without that line, an agent that finds twelve unused UI components will reasonably decide they are dead code and offer to delete them. Marking something as intentionally incomplete is as important as documenting what is finished.
What Does Not Belong In It
This matters equally, because the temptation is to write everything.
Anything the code already says clearly. A list of every endpoint with its parameters goes stale within one sprint, and the agent reads the router faster than you maintain the list. Ours describes the blog endpoints in two sentences of behaviour — drafts are hidden from anonymous callers, publishing is a PUT with { status } — and lets the signatures live in the code.
Secrets. The file names the environment variables and says where they are set. It never holds a value.
Related warning from our own setup: the .claude/settings.local.json file beside it can collect real connection strings inside allowlisted commands. It is gitignored for that reason. Do not screenshot it for a blog post.
Tutorial content. It is not there to teach Angular. It is there to explain this codebase.
Anything you will not maintain. A wrong document is worse than a missing one, for an agent and for a person, because both will trust it.
The Unexpected Result
We started writing CLAUDE.md to make the AI agent more effective. The side effect was better human documentation than we ever managed to write on purpose.
The reason is simple. Documentation written for people leans on shared context — "you know how our auth works" — and it always does, because writing that context out is boring and the reader probably knows it. An agent has no shared context and cannot ask the person at the next desk. So writing for it means writing the thing down.
Then a new person joins, reads the file, and is productive the same afternoon.
There is a second effect also. Normal documentation rots because nobody notices when it goes stale. Here the agent acts on the stale line and you find out in the next session. So updating it becomes part of the work, not a task for a quarter that never comes.
How to Start One
Do not sit down and write it. You will produce a wiki page nobody reads.
Write it as a byproduct of being surprised. Every time you or the agent gets something wrong for a reason that was not visible in the code, add three lines — what the surprise was, what the real rule is, and why.
Ours reached 16KB entirely this way. Every paragraph in it earned its place by costing somebody time first.
Two habits keep it correct.
- Update it in the same commit as the change. A separate docs commit is a docs commit that does not happen.
- When the agent misunderstands something, fix the file, not only the code. The wrong output is the symptom. The missing paragraph is the bug.
Conclusion
Your code holds what your system does. Your CLAUDE.md holds what it means.
Only one of those can be recovered by reading the repository later. Write down the other one.
Reference
Comments
Be the first to comment.