TL;DR
After six months of running Claude Code on real projects, I learned that the single highest-leverage file in my repo isn't code β it's CLAUDE.md, the project instruction file the agent reads at the start of every session. Most of mine started as a dumping ground and quietly stopped working. Here are the 5 rules that fixed it, with before/after examples. π‘
The Problem
If you use Claude Code (or any coding agent that supports a project instruction file), you've probably done what I did: every time the agent made a mistake, you added a rule.
"Don't use var." Added. "Always run tests before committing." Added. "Remember we use pnpm, not npm." Added.
Three months in, my CLAUDE.md was 400+ lines. And here's the uncomfortable part: the agent was following it less, not more.
It wasn't random. The file had become the equivalent of a legal terms-of-service page β technically complete, practically unread. Long instruction files don't fail loudly; they fail by dilution. Every low-value line you add taxes the attention paid to the lines that actually matter.
This mattered to me because I run agents with minimal supervision. When I'm reviewing every keystroke, a sloppy instruction file costs little β I just correct the agent live. When the agent runs a task end-to-end on its own, the instruction file is the supervision. If it's bad, nobody catches the drift until the damage is done.
So I rewrote mine from scratch, watched what actually changed behavior over the following weeks, and kept notes. Below is what survived contact with reality.
(Context for the examples: Claude Code, mid-2026 releases, on macOS. The principles apply to any agent that ingests a per-project instruction file β Cursor rules, Copilot instructions, etc.)
How I Solved It
Rule 1: Write a table of contents, not an encyclopedia
The biggest structural change: CLAUDE.md stopped containing information and started pointing to it.
Before, everything lived inline β architecture notes, style guides, deployment steps. After, the file is ~80 lines: what this project is, what the agent's scope is, and a routing table telling it where the details live.
## Docs map
| Need to know... | Read... |
|---|---|
| Current status & next task | STATE.md |
| Why past decisions were made | DECISIONS.md |
| Architecture & module layout | docs/architecture.md |
| Release process | docs/release.md |
Claude Code supports @path/to/file.md imports for content that should always be loaded. I use those sparingly β only for rules that must apply to every single session. Everything else is a plain path the agent reads on demand.
Why it works: the agent reads the short file completely instead of skimming the long one. And when a detail changes, I update one topic file instead of hunting through a monolith.
Rule 2: Separate "always true" from "true right now"
This one bit me hard. I used to write current status into CLAUDE.md: "We are currently migrating the auth module." Two months later that line was stale, the migration was done, and the agent kept making decisions as if it wasn't.
The fix is an explicit split:
-
CLAUDE.mdβ things that are true for the life of the project. Stack, conventions, scope, personas. -
STATE.mdβ things that are true this week. Current task, blockers, recent decisions. The agent reads it first, every session, and updates it before the session ends.
Then I added one line to CLAUDE.md that turned out to be load-bearing:
## Conflict resolution
If STATE.md contradicts CLAUDE.md, STATE.md wins (it's newer).
Priority: STATE.md > STRATEGY.md > CLAUDE.md > older logs.
Agents hit contradictory instructions constantly β stale docs, half-finished migrations, your own inconsistent notes. If you don't tell them how to break ties, they pick arbitrarily, and each session picks differently. An explicit priority order made behavior noticeably more consistent across sessions than almost anything else I tried.
Rule 3: Define scope by what the agent must NOT touch
Positive scope ("you work on the payments service") sounds sufficient. It isn't. Agents are curious by default: give one a monorepo and a vague task, and it will happily wander into neighboring services "for context" β burning tokens, and occasionally acting on code it has no business reading.
Negative scope fixed this:
## Scope
You own `services/payments/` only.
Do NOT read or modify sibling services (auth/, catalog/, admin/),
even to "understand the overall system". If a task genuinely
requires another service, stop and say so instead.
The key phrase is the exception path: stop and say so. Without it, a hard prohibition just teaches the agent to work around missing context silently. With it, you get a clean escalation instead of a guess.
I also list explicit exceptions (shared libraries, config that lives outside the service). Boundaries with documented exceptions get respected; absolute boundaries get rationalized away the first time they're inconvenient.
Rule 4: Every rule earns its place with a "why"
Compare:
- Do not use the staging database for tests.
versus:
- Do not use the staging database for tests β it's shared with
the demo environment, and test writes have corrupted live
demos twice.
The second version costs one more line and works dramatically better. Not because the agent "cares", but because a reason lets it generalize correctly. Given the bare rule, an agent facing a novel situation (a new script that reads staging?) has to guess the rule's intent. Given the reason, it can derive the answer: reads are fine, writes are the hazard.
This also forces a useful discipline on me: if I can't articulate why a rule exists, it's usually a stale reflex from an incident that no longer applies β and it gets deleted instead of added.
My heuristic now: a rule without a reason is a bug report waiting to happen.
Rule 5: Prune on a schedule, not on failure
Instruction files rot silently. Nothing alerts you when a line stops being true; the agent just starts acting slightly wrong, and you blame the model.
So I treat CLAUDE.md like dependencies: scheduled maintenance. Once a month I do a 15-minute pass with three questions per line:
- Is this still true? (Stale facts are worse than missing facts β they're confidently wrong.)
- Has the agent actually violated this recently? If a rule hasn't been relevant in months, it may be dead weight.
- Did I add this in anger? Post-incident rules are usually overfitted to one failure. Rewrite them as the general principle, or delete them.
My file has shrunk in four consecutive monthly passes. Behavior improved each time. I no longer believe that's a coincidence β I think instruction-file quality is closer to signal-to-noise ratio than to coverage.
The shape that emerged
graph TD
A[CLAUDE.md<br/>~80 lines, stable truths + routing] --> B[STATE.md<br/>current task, updated every session]
A --> C[docs/architecture.md<br/>read on demand]
A --> D[docs/release.md<br/>read on demand]
B -->|wins conflicts| A
One small stable file, one volatile file with clear supremacy, and on-demand depth. That's the whole system.
Lessons Learned
- Instruction files fail by dilution, not by absence. The 400-line file performed worse than the 80-line file. Every line you add taxes every other line. Ruthless brevity is a feature, not laziness. βοΈ
- "True forever" and "true this week" must live in different files. Mixing them guarantees staleness, and staleness is worse than silence β the agent trusts your stale line more than its own observation.
- Explicit conflict-resolution order beats more rules. One line β "newest state file wins" β eliminated a whole class of inconsistent-between-sessions behavior. Your docs will contradict each other; plan for it.
- Negative scope with an escalation path is the only scope that holds. "Only work on X" gets creatively reinterpreted. "Don't touch Y; if you think you need Y, stop and ask" actually holds.
- Reasons are compression. One rule + its why covers dozens of unwritten variants, because the agent can derive them. Bare imperatives cover exactly one case: the last incident. β οΈ
What's Next
Two experiments I'm running now:
- Per-directory instruction files for the areas where the agent keeps making the same class of mistake β pushing rules closer to the code they govern, so they only load when relevant.
-
Making the agent propose its own edits: at the end of a session, it suggests a diff to
STATE.mdand flags anyCLAUDE.mdline it found stale or contradictory. Early results are promising β the agent is surprisingly good at noticing when my instructions disagree with my codebase.
I'll write up the per-directory experiment once it has a month of runtime behind it.
Wrap-up
If you take one thing from this post: open your instruction file right now and delete ten lines. I'm serious. Odds are you'll find stale facts, anger-rules, and duplicated context β and your agent will behave better without them.
If you found this useful, follow me here on Dev.to β I write about running AI coding agents on real projects, including the failures. And if you've found instruction-file patterns that work (or spectacular ways they've failed), I'd genuinely love to read them in the comments. π
Top comments (0)