GnothiGnothi
SeriesFieldsCommunityPublishing
Sign inGet started free

2. Writing the Instructions Your Next Session Will Actually Read

Summary

The founding charter

The core of this chapter is CLAUDE.md, the file Claude Code loads automatically at the start of every session before your first request arrives — no attaching, no pasting, just a file sitting at the project root or inside the .claude directory that gets read on its own. It walks through the four places instructions can live — the shared project file, a personal file in your home directory, an untracked local variant, and organization-wide settings — and explains that Claude Code doesn't let the narrowest file override the others; it concatenates all of them into context, broadest first, and leans on the model to treat the more specific instruction as the one that wins.

From there the chapter builds an actual file for a shared-list app: starting from a /init draft, keeping only the commands that aren't obvious (the dev server, the type check, the one authoritative test command), naming the manual click-through journey that tests alone can't replace, writing down two or three real architectural constraints, and stating flatly what "finished" means. It explains why the file should stay short — it's re-injected on every turn, so bloat taxes every future question — and points to /context, /doctor, and /memory as ways to check what's loaded and trim what doesn't earn its place. It draws a hard line between guidance, which the model can still talk itself past, and real enforcement through a permissions deny list, and closes with a repeatable loop for testing whether an instruction actually changes agent behavior: fresh session, one-line request, watch what's skipped, edit one sentence, retest.

What changed in how instructions get loaded

A shorter section covers a real gap in that mental model: the built-in Explore and Plan subagents skip reading CLAUDE.md and skip checking Git status entirely, while custom subagents you define still inherit it. It also notes a new flag for passing subagent rules via a file instead of a command-line string, and a fix in recent releases removing an approval prompt that used to stall scripted runs using organization-delivered instructions — all catchable with the same /context check.


Last time you and the agent stopped blank items slipping into the shared-list app, checked it three ways, and saved one commit. Now close that session.

Open a new one and ask for a second small change.

Watch what happens. The agent does not know how you start the app. It does not know which test command is the one that counts. It does not know what you mean by done. So it guesses. It reads a little of the project and invents a script name that does not exist. Or it runs a test file directly instead of the suite. Or it edits the wrong layer, putting a validation check in a component when the project keeps that logic somewhere else. Or, most commonly and most annoyingly, it announces that the change is complete without having run anything at all.

None of that is the agent being careless. Everything you explained by hand last chapter, the layout of the project, the command that matters, what finished means, lived in that conversation. The conversation is gone. The repository is what survived, and the repository never said any of it.

So say it in the repository, in a file the next session reads before it does anything else.

That file is called CLAUDE.md, and Claude Code loads it automatically. You do not attach it, mention it or paste it. When you launch a session inside a directory, Claude Code looks for that file and pulls its contents into the session's context before your first request arrives. Put it at the root of the repository, or inside the project's .claude folder; both are discovered. Because it sits in the repository, you commit it, and it travels with the code. A teammate who clones the project gets your instructions along with your source. A fresh session you start six weeks from now gets them too, without you remembering anything.

Writing the file

Do not start from a blank page. Type slash init in the project, and Claude Code reads the repository, the package manifest, the build configuration, the test setup, the shape of the source tree, the Git history, and writes a first CLAUDE.md for you. Let it. That draft will be too long and partly wrong, and both of those are fine, because editing something down is faster and more accurate than remembering everything from nothing.

Then you edit, and most of the editing is deletion.

Keep the commands that are not obvious. For our app that means the development server command, npm run dev, so a session knows how to bring the thing up. The type check, which is the TypeScript compiler run with the no-emit flag, because a session that only runs tests will happily leave you a type error. And the one test command that counts. Write down which one is authoritative, the whole suite, not a single file, and, separately, write down how to run a single test file when the agent is iterating, because that saves it guessing at the argument syntax. Also note how migrations run, whatever your tool is, because that is a command nobody infers correctly.

Now name the manual journey. The test suite is one check; a person clicking through the app is a different check, and last chapter you did both for a reason. Write the journey down in one sentence: open a list, type an item, add it, see it appear. That sentence is what turns "tests pass" into "the feature works," and if it is not in the file the agent cannot be expected to invent it.

Then two or three architectural constraints this app actually has. General programming advice does not count. Ours has three worth writing. Database access goes through the client in the database folder; no handler writes raw SQL. Validation for adding an item lives in one place, the place last chapter's fix put it, so nobody adds a second competing check in a component. And a schema change requires a migration file, never a hand-edited database, because a hand edit works on your laptop and nowhere else.

Finally, the definition of a finished change, stated flatly, because this is the sentence that stops the agent declaring victory early. A change is finished when the diff is small enough to read, the test suite passes, the manual journey has been clicked through, a regression test covering the specific mistake exists, and the work is one commit with a plain message. Say the commit format you want. Ours is the conventional style with a prefix like fix or feat.

That is close to the entire file.

What to leave out is a longer list. Leave out the plainly discoverable. If the test command is just npm test and it sits in the manifest where anyone can see it, writing it down buys nothing. Leave out generic coding standards and anything that reads like framework documentation; the model already knows React, and your paraphrase of the docs will be worse than the docs. Leave out a file-by-file inventory of the project, which is stale the week after you write it. Leave out today's bug, this sprint's checklist, and any note that will be false within a month. And leave out full API dumps and long procedures.

One test decides whether a line earns its place: include a rule only if leaving it out makes the agent make a predictable mistake. If you cannot name the mistake, delete the line.

The reason for that strictness is mechanical. This file is re-injected on every single turn, as part of what the model receives with each request. A long instruction file is therefore a tax on every question you ask for the rest of the project's life. It eats the room the agent needs to actually read your code. Worse, it dilutes itself: as the instruction block grows, the model starts missing the subtle directives buried inside it, and the sentence you cared most about is the one that gets skated over. A bloated file also pushes the session toward compaction sooner, which means the conversation starts getting squeezed earlier than it needed to.

So aim for roughly two hundred lines at the root, a few thousand tokens. Two helpers keep you honest. Slash context shows where your tokens are going and lists every instruction file currently loaded, under a memory files heading. Slash doctor audits the project and points out instructions that are redundant because Claude already infers them from the codebase, which is a fast way to find the lines your slash init draft added that you should now delete. There is also slash memory, which lists the active memory locations and opens the one you choose in your editor mid-session, so you can fix a bad sentence the moment you notice it.

A line beginning with an at sign followed by a path is an import, and it keeps the file small. The contents of that file get pulled inline into the context when the instructions load. So you can point at your existing database schema notes instead of copying them into the instructions and then letting the copy drift out of date. If you want to mention a file path in prose without importing it, wrap it in backticks and the import does not fire. And the first time a project imports a file from outside itself, Claude Code stops and asks you to approve it, because an import is a way for content you did not write to enter your session.

Where else instructions live

The project file is one of four places instructions can come from, and knowing which is which saves you from putting a private database password somewhere a hundred strangers can read it. Facts about this project that are true for every person on every machine belong in the project file.

Above it sits a personal file, in a folder called .claude in your home directory. That one follows you across every project you open. It is not committed anywhere. It is the right home for how you like to work: that you want commit messages in lower case, say, or that you prefer to see a plan before any file gets written. If you find yourself typing the same sentence into unrelated repositories, that sentence belongs in the personal file. Claude Code will also read a rules folder alongside it, if you would rather split those preferences into separate files than keep one long one.

Below the project file sits a local variant, named CLAUDE.local.md, in the repository root but not shared. This one is for facts true on your machine only: the connection string for the Postgres database you run locally, the port your dev server is on because something else already owns the usual one, a debug flag you personally like enabled. Claude Code does not add that file to your ignore list for you. If you create it and do not add its name to the project's .gitignore, you will commit your local connection string to a public repository. Add the ignore entry in the same minute you create the file.

The fourth layer you will probably never touch yourself, but it explains behavior you might otherwise find mysterious. A machine can carry organization-wide instructions installed at the system level, under a system application support folder on macOS, under a claude-code folder in the system configuration directory on Linux, under the installed program folder on Windows, and an employer can push those out centrally through a managed settings key. If you are on a work laptop and the agent seems to have opinions nobody in your team wrote down, that is where they came from.

Precedence trips people up because they expect the more specific file to replace the more general one. Claude Code does not overwrite anything. It concatenates. Every instruction file it finds goes into the context window in order, broadest first: the organization file, then your personal one, then the project one, then any subdirectory files on the way down to where you launched, then the local untracked one last.

So all of it is present at once, and conflicts are resolved by the model reading the pile and reasoning about it, with no rule engine involved. In practice the later, narrower instruction wins, because it reads as the more specific and more local statement, the same way a sticky note on one drawer beats a sign on the front door. But that is a tendency, not a guarantee. If a project rule has to override something in your personal file, do not rely on position alone. Write in the project file that the rule is important, or that the agent must do it. Blunt, emphatic wording in the narrow file is how you make sure a broad habit does not quietly survive.

Wishes and walls

CLAUDE.md is guidance, not enforcement. It is text handed to a language model with the hope that the model follows it. Usually it does. Sometimes it does not, especially if the file is long or the instruction is buried or two lines pull in different directions. Nothing in the system stops the agent from doing something your instructions forbid. If you write "never reset the database" and the agent is convinced a reset is the right move, that sentence is a request it can talk itself past.

Real limits live in the project's settings file, in the permissions deny list. There, the runtime tool layer refuses to execute a matching command, checked before the command runs. A deny rule beats an allow rule and beats the model's intention. So for our app, the instructions say schema changes go through migrations, and the settings file denies the reset command outright, and denies dropping a database. The instruction explains the intent; the deny rule makes the worst case impossible.

Hooks, hosted runs and unattended work later on all depend on knowing which of your rules are wishes and which are walls. Write the wish, then build the wall for anything you cannot afford to have gone wrong.

Proving a fresh session can do the work

Instructions you have not tested are just an opinion about your project. So test them the way the failure actually happens: from nothing.

Commit the instruction file and the settings file first. Their own commit, with a message that says what it is, instructions and denies, not bundled in with a feature. Then start a fresh session. Not a resumed one, not the one still warm on your other tab. A new session, in the project directory, with no history.

Do one cheap sanity check before you ask for work. Run slash context and look at the memory files list. Your project instructions should be named there. If they are not, nothing else in this test means anything, and the usual cause is boring: the file is in a different directory than you think, or you are launched somewhere other than the project root.

Now give a one-line request that only succeeds if the file was read. Ask for a second small validation rule on the same add-item form: reject an item longer than some sensible limit, say two hundred characters. One sentence. Say nothing about the test command, nothing about the layer the validation goes in, nothing about what done means.

Then sit on your hands and watch which things the agent does without being told.

Does it put the length check in the same place as the emptiness check, or does it invent a second validation site in the component? Does it run the authoritative test suite, by name, or does it make up a script? Does it run the type check? Does it add a regression test for the too-long case? And when it reports back, does it report in the terms your file defined, suite green, journey clicked, one commit, or does it just say the change is complete?

Each of those is a line in your file. Each answer tells you whether that line works.

When the agent skips the manual journey, your file is unclear, and the fix belongs in the file. If you wrote something vague like "verify the change works," the agent has satisfied that by running tests. So rewrite the sentence into something that cannot be satisfied any other way: state that a change is not finished until a person has opened a list, typed an item, added it and seen it appear, and that a passing test suite alone does not count.

Then throw the change away with git restore or git revert, both from last chapter, and run the same one-line request again in another fresh session. Same request, changed file. If the behavior changes, the sentence was the bug and you fixed it. If it does not, the sentence is still ambiguous, or it is buried too deep in a file that has grown too long, or it conflicts with something in your personal instructions pulling the other way. Make it blunter and more emphatic, or make the file shorter so it stands out, and go again.

Request, observe the gap, edit one sentence, retest identically. It is the change loop from last chapter, aimed at the instructions instead of the code. Keep the request fixed so the only variable is the wording.

When it passes, commit whatever you edited. The repository now carries two things: a change, and the rules that produced it. A stranger cloning it gets both.

Instructions describe a project: its commands, its shape, its bar for done. They do not help the agent follow a code path it has never seen, or reproduce a defect that somebody else reported in words. That is next.

What changed in how instructions get loaded

Most people assume every part of a session sees the project instructions. It does not. Anthropic's documentation now spells out the scoping rules for subagents, the separate, bounded helpers Claude Code spins up for particular jobs, and the two built-in ones behave differently from the ones you write yourself. The built-in Explore and Plan subagents skip reading CLAUDE.md files and skip checking Git status. That is deliberate: it keeps their context small and their responses fast. Custom subagents that you define do inherit the project instructions and the automatic memory.

That hits the task you would least expect. If you write an architectural constraint into your instruction file, database access only through the client in the database folder, say, and then ask for a plan for some larger feature, the planning pass may not have that constraint in front of it. The plan comes back proposing raw queries in a handler. You then blame the instruction file, which never got read.

Smallest useful action, two parts. Run slash context and look at what is actually loaded and where your tokens went, so you stop guessing. Then, for a requirement that must survive an exploration or planning pass, put it in the request itself, or in the definition of a custom subagent, instead of trusting the project file to carry it. One sentence in the prompt is cheap insurance.

Two smaller items from the September releases, if you work in that territory. There is a new command-line flag, append-subagent-system-prompt-file, in version 2.1.261, which does what the existing string version did but reads the rules from a file on disk instead of a command-line argument. That exists because shells cap how long an argument can be, and a substantial set of review rules blows past that cap. If you run non-interactive passes, a batch code review, a sweeping refactor, put the subagent rules in a file inside your .claude folder and pass the path. The string and file forms are mutually exclusive, so pick one.

And in versions 2.1.219 and 2.1.234, organization instructions delivered through the managed claudeMd setting stopped raising an interactive approval prompt at launch. Previously that prompt could stall a scripted, non-interactive run, a script sitting there waiting for a keypress nobody was there to press. Shell and sandbox confirmations are untouched; only that one startup approval went away. If your team was wrapping Claude Code in a script to inject shared standards, distribute them through that managed setting instead.

The check for all three is the same and takes a second: slash context, and read the memory files list. If what you expect is named there, the mechanism worked. If it is not, no amount of careful wording in the file will matter.