What I Put in CLAUDE.md After 50 Commits With It

The CLAUDE.md entries that actually earned their place on a real project — mistake-driven rules, not a speculative template you copy once and forget.

TL;DR: Most “CLAUDE.md best practices” posts are written before the author has felt any pain — a tidy list of ten sections you should include. This is the inverse. After 50-odd commits with an agent on one real project, the entries that survived are the ones I can trace to a specific failure: an hour I burned, a test I tripped, an assumption the agent made twice. The durable rule is to stop writing CLAUDE.md speculatively and let it accrete from real mistakes. But accretion has a cost: every line is read on every single turn, so a bloated file is a tax you pay forever. A good CLAUDE.md is a changelog of lessons you’ve actually learned, ruthlessly curated — not a style guide you aspire to.

Nobody’s day-one CLAUDE.md survives contact with the project

Here’s the pattern I see, and the one I fell into myself. You start a new project with an agent, you’ve read that context files are important, so you sit down and write an aspirational CLAUDE.md. “Use functional patterns.” “Write tests for everything.” “Follow the existing conventions.” “Prefer composition over inheritance.” It feels productive. It looks like a real engineering document.

Then you start working, and within a day half of it is dead weight. The agent ignores “follow the existing conventions” because that’s not an instruction, it’s a mood. It already writes tests, so that line did nothing. And meanwhile the actual problems — the ones that cost you real time — aren’t in the file at all, because on day one you didn’t know they existed yet.

To be clear about what this post is and isn’t: I’m not trying to out-document Anthropic. Their docs tell you what CLAUDE.md can do, and they do it well. This is the thing docs structurally can’t give you — the specific entries one real project earned the hard way. And it’s the inverse of the “ten sections you should include” template essays, which are written before the author has felt any pain. Those are guesses about what might go wrong. What follows are receipts for what actually did.

That’s the core mistake: a speculative CLAUDE.md is a guess about what will go wrong, written at the exact moment you know the least about the project. The good entries can’t be guessed. They have to be earned, by something actually going wrong first.

I’ve written before about treating AI-assisted engineering as a genuinely new workflow rather than autocomplete with extra steps. The context file is where that workflow gets its memory — and the test for what goes in it turns out to be simple: did this line get earned by a failure? Let me walk through the entries on a real project that did.

The best entries are scar tissue from a specific wasted hour

The single most valuable line in my CLAUDE.md pins the local dev server to a non-default port.

That sounds trivial. It is not trivial. The default port on this machine is already held by an unrelated process from a different project. So when the agent runs the dev server on the default port, one of two things happens: the server fails to bind outright, or — much worse — a health check or a curl quietly hits the other app and starts returning 404s for routes that exist perfectly well in the app we’re actually working on. The first time this happened, the agent and I spent a genuinely embarrassing stretch debugging a routing problem that did not exist. The routes were fine. The request was going to the wrong server.

You cannot derive that from the code. There is nothing in the repository that says “by the way, another process squats on this port.” It is a fact about this machine and this developer’s environment, and it is exactly the kind of thing that belongs in a context file. So the entry says, in effect: always start the server on this specific port, here’s the exact command, and here’s why — so the agent doesn’t helpfully “fix” it back to the default later.

The transferable principle: the highest-value entries describe operational reality that isn’t visible in the code. Port conflicts, a staging environment that behaves subtly differently from production, a build step that has to run before another one, a service that takes 30 seconds to warm up so the first request always times out. None of this lives in the source. All of it costs you an hour the first time you hit it. Write it down the moment it bites you, with the why attached, and it never costs anyone that hour again.

This is the part no template can hand you. Anybody can write “document your environment quirks.” Nobody but me could have written that other app holds the default port on this machine, so pin the dev server to a different one — because that entry only exists because the two of us lost an afternoon to it. The receipt is the value. The next two are cut from the same cloth.

If a test keeps failing the same way, that’s a CLAUDE.md entry

The second category of earned entries comes from tests. Specifically, from watching the agent trip the same test the same way more than once.

On this project there’s a content taxonomy — a fixed set of allowed tags — that’s enforced by a test. A bunch of older, more specific tags were consolidated into a smaller canonical set, and the test now fails the build if a new post uses one of the retired tags. This is a reasonable design. It keeps the taxonomy from sprawling. But the agent doesn’t know the history. Left to its own judgment, it picks tags that are perfectly sensible in the abstract — and some of them happen to be exactly the retired ones. So it writes a post, runs the test, the test fails, it fixes the tags, moves on. Then next week it does the same thing on the next post.

That’s the tell. A mistake the agent makes once is noise. A mistake it makes twice is a missing CLAUDE.md entry. So the file now spells out the consolidation: here are the retired tag slugs that will fail the build, here’s the canonical set to use instead. The agent stopped tripping that test. The same logic produced a sibling entry about an SEO title-length constraint another test enforces — there’s a documented escape hatch (an override map) for the rare long title, and the entry points the agent straight at it instead of letting it discover the failure the hard way.

Notice what these entries are not. They are not me re-explaining what the test does — the test is right there in the repo, the agent can read it. They’re explaining the part the test can’t tell you: the intent behind the constraint, the history that produced it, and where the escape hatch is. That’s the operational layer on top of the enforced layer.

Stop the agent from “fixing” things that are supposed to be that way

There’s a subtler failure mode, and it produced one of my favorite entries.

In this repo, one specific file shows up as deleted in git status at the start of every single session. Every time. That’s the intended state — it’s a local artifact, not something that should be committed or restored. But a diligent agent sees an uncommitted deletion and reasonably concludes something is wrong. It tries to be helpful. It stages the deletion, or worse, it tries to restore the file — and now there’s a phantom file back in the tree that wasn’t supposed to be there. Either way it’s doing work I never asked for, on a “problem” that isn’t one, and I have to notice and unwind it.

So there’s a one-line entry, and it’s exactly the kind of receipt a template can’t produce: this file always shows as deleted, that’s expected, do not stage it, do not restore it, leave it alone. You only know to write that sentence after watching an agent “fix” it once.

The principle here is about pre-existing quirks the agent will mistake for bugs. Every mature codebase has a few. A linter rule that’s intentionally disabled in one directory. A test that’s skipped on purpose. A “TODO” that’s actually a permanent decision nobody’s going to act on. A generated file that looks hand-editable but isn’t. An agent has no way to distinguish “this is broken” from “this is deliberately weird,” and its default disposition is to fix things. Left undocumented, it will keep re-litigating the same settled decision every time it stumbles across it. One sentence in CLAUDE.md — this is intentional, don’t touch it — ends that loop permanently.

This ties directly to knowing when to trust an agent and when to step in: a lot of a context file’s value is telling the agent where its default helpfulness is actively counterproductive.

Some lessons are better encoded as hooks than as prose

Not every recurring failure should be solved with words. Some of them should be solved with automation, and CLAUDE.md should just point at the automation.

Two examples from this project. First, a formatting hook: every time the agent edits a source file, the formatter runs automatically afterward. Before it existed, the pre-commit check would occasionally fail purely on formatting, and the agent would notice, re-run the formatter, re-commit — a small tax, paid repeatedly. The hook makes that whole class of failure impossible. Second, a validation hook on a critical JSON data file the app loads at compile time: if an edit would leave that file as invalid JSON, the edit is blocked outright, instead of breaking the build in a way that surfaces much later.

The CLAUDE.md entries for these don’t re-explain what formatting is. They document that the hooks exist and what they guarantee — so the agent (and future me) understands why pre-commit never trips on formatting and why that data file can’t silently go corrupt.

The principle: when a lesson can be enforced mechanically, enforce it mechanically, and let CLAUDE.md describe the guardrail rather than nag about the behavior. A rule that says “always run the formatter” is a rule the agent has to remember every turn. A hook that runs the formatter is a rule that can’t be forgotten. Prose is for things you can’t automate; hooks are for things you can. I go deeper on the full set of mechanical guardrails in my Claude Code resource bible, but the short version is: a hook is a CLAUDE.md entry that enforces itself.

“Verify before you act on it” is a rule worth its weight

One more earned entry, and it’s the one I’d transplant into almost any project.

This site has run automated audits — SEO audits, security scans, multi-agent review pipelines. Useful tools. But the first big audit came back with a findings list that was roughly one-sixth false positives. Things it flagged as missing were already wired up. Things it claimed were misconfigured were correct. If I’d let the agent just act on that list, it would have “fixed” a pile of things that weren’t broken, and some of those fixes would have introduced real regressions into working code.

So there’s a standing rule: when any automated audit produces findings, verify each finding against the actual current state of the code before reporting or fixing it. Treat the audit as a list of suspects, not a list of verdicts.

This generalizes way past audits. Any time a tool generates a to-do list for the agent — a linter, a dependency scanner, a migration assistant, an LLM-as-judge — the failure mode is the agent treating the output as ground truth and acting on it wholesale. The CLAUDE.md entry is a posture: confirm before you act. It’s also the backbone of my daily agentic workflow — the agent generates a lot of candidate work, and the discipline lives in the verification step, not the generation step.

Every line is read on every turn, so prune like it costs you

Here’s the part the speculative-template posts always skip. Accretion is only half the job. The other half is curation, and it’s the harder half.

Your CLAUDE.md is loaded into context on every turn. It isn’t free. A 2,000-line context file is 2,000 lines the model is reading before it even looks at your actual question — every single time. Bloat costs you tokens, it costs you attention budget, and past a certain size it starts actively diluting the entries that matter. The crucial port rule is competing for attention with whatever low-value filler crept in around it. Worse, a stale entry — a rule that describes how things used to work — is actively harmful: it confidently points the agent at the wrong thing.

So I prune. When an entry’s reason for existing goes away — the quirk got fixed, the constraint got relaxed, the workaround became unnecessary — the line comes out. The file is not an archive. It’s a working set. The bar for staying in is the same as the bar for getting in: does this describe an operational reality that isn’t obvious from the code, and is it still true?

If you only take one operational habit from this post, make it this: treat your CLAUDE.md like a hot cache, not a wiki. Small, current, every entry pulling its weight. The moment it gets long enough that you stop reading it yourself, the agent’s effectively stopped reading it too.

What does NOT belong in CLAUDE.md

The curation bar is easier to hold if you have a clear list of things to keep out. Mine:

Anything the code or git history already says. Don’t document your directory structure, framework version, or test command if they’re discoverable in seconds. Duplicating the repo just creates two sources of truth that drift apart — and the copy in CLAUDE.md is the one that goes stale.

One-off conversation context. “We decided to use approach X for the payments refactor” is a fact about one task, not a standing rule. It belongs in a commit message, a PR description, or an issue — somewhere scoped to the work. In CLAUDE.md it’s clutter that’s irrelevant 99% of the time.

Aspirational style nobody enforces. “Write clean, maintainable code.” “Prefer clarity.” These read as instructions but function as decoration. If you actually care about a style rule, encode it mechanically — a linter rule, a formatter config. If it can’t be enforced, the agent can’t reliably follow it, and the line is just taking up budget. Vibes are not a CLAUDE.md entry.

Generic best practices the model already knows. It knows what SQL injection is; it doesn’t need you to say “parameterize queries” in the abstract. What it does need is the project-specific version: “this codebase wraps all DB access behind module X, never query the repo directly.” Generic, out. Specific-to-here, in.

The rubric: does this line earn its place?

So here’s the test I actually apply, every time I’m tempted to add a line.

1. Did a real failure earn this? Can I point to the wasted hour, the tripped test, the assumption the agent made twice? If I’m writing it speculatively — guessing at a problem I haven’t hit — it doesn’t go in yet. I wait for the failure. The failure is the proof that the entry is load-bearing.

2. Is it invisible in the code? If the agent could learn this by reading the repo, the repo is the right place for it, not CLAUDE.md. The file is for operational reality that the source doesn’t reveal: environment quirks, intent behind constraints, deliberate weirdness, the why behind a rule.

3. Is it still true? Re-read the file periodically with a delete finger ready. Every entry that describes a fixed problem or a relaxed constraint is now worse than useless — it’s misinformation. Out it goes.

4. Could a hook do this better than a sentence? If the lesson can be enforced mechanically, enforce it mechanically and let the prose just describe the guardrail. A rule that can’t be forgotten beats a rule the agent has to remember on every turn.

A line that passes all four earns its place. A line that fails any of them is costing you more than it gives.

That’s the whole philosophy. Don’t write your CLAUDE.md the way you’d write a style guide — front-loaded, aspirational, comprehensive, and mostly ignored. Write it the way you’d keep a changelog of lessons: one entry per scar, each traceable to the day it was earned, ruthlessly pruned when it stops being true. The speculative version looks more impressive on day one. The earned version is the one that’s still saving you time on commit fifty.