23 July 2026·7 min read

Why I Still Use a CLAUDE.md

I use CLAUDE.md to record the storage, authentication, sync, and package-boundary decisions that coding agents cannot reliably recover from code alone.

Yes, I still use a CLAUDE.md, even with Claude Fable 5 and GPT-5.6.

I left it out of a new side project. The project has not launched, so I am leaving out its name and purpose. It has a client, a hosted service, and private user data that has to stay in sync between them.

The repository looked small, and I knew the whole system, so I went straight into building features. During the first three days, I made 112 commits to the client.

By then, UI code and background tasks could update the same local data through different paths. A settings migration could clear a valid login token. Sync treated an empty server response as a reason to upload whatever was still cached on the device. I had never recorded the rules connecting those parts of the application, and the behavior depended on which code path ran.

That is what I use CLAUDE.md for. It records decisions an agent cannot reliably recover from the current code: who owns a piece of data, which package a dependency belongs in, how to verify a change, and which mistakes should not be repeated. I keep an AGENTS.md beside it as a pointer for tools that look for that filename.

I already use the same approach on Airbase, where different packages in the service monorepo need local instructions. On SCNZ, the file records a boundary that keeps its media runtime independent from projects and scenes. The side project was the exception.

The first bug was a stale write

Several UI flows read a collection from local storage, changed the snapshot held by the rendered component, and wrote the whole collection back. Background work could update the same key between that read and write.

The storage API accepted both writes. Whichever full snapshot arrived last won, even when it was based on older state. Entries added by the background task would disappear without either operation reporting an error.

I moved the call-site read-modify-write sequences behind store.update(key, current => next), so mutations were applied through the shared storage layer instead of against a component's copy. The commit ended up with the title “Atomic list updates everywhere.”

I remember the “everywhere” part because it meant the fix was larger than the bug. I had relied on every feature author to remember the correct update protocol. That should have been a repository rule: do not write to storage directly; use the store's mutation path.

Auth and sync exposed the same gap

The next issue came from a settings migration. It found an obsolete local value and cleared the login token along with it. The migration ran during startup, so reloading the application could sign a user out.

Cleaning up an old settings shape and deciding whether a session is valid are separate responsibilities. The local migration should never have owned both. The corrected rule is that the token is cleared on explicit sign-out, while the server decides whether the session is still valid.

The sync bug was worse. When a pull returned an empty collection, the client treated it as missing server data and uploaded the current local cache. If a person signed out of account A and then signed into account B, data still on the device could be uploaded through B's authenticated session.

Server-side authorization worked correctly and accepted a request made by account B. The client was the problem: it had treated data cached for A as though it now belonged to B.

We changed the sync semantics rather than patching that one path. The server database is authoritative. An empty remote collection clears the local mirror. Changing accounts discards queued writes from the previous account, and deletes are sent explicitly instead of being inferred from a missing row.

Storage, auth, and sync looked like separate bugs while I was fixing them. They all came from decisions about ownership that had been left for individual code paths to make.

The first CLAUDE.md was incomplete

I wrote the first proper CLAUDE.md after the stale-write bug and before I found the auth and cross-account issues.

The first version already told agents to use the storage layer, keep network calls in one place, and respect a few product rules. It said nothing about which side was authoritative during sync, because I had not settled that model properly myself.

I do not expect the file to prevent every bug. I use it to keep a decision from being lost once the codebase has shown me that the decision matters. I fix the implementation, add a regression test when one is useful, and update the project rule in the same change.

Without the last step, another agent can reintroduce the same assumption in a different feature without touching the code I just fixed.

What I put in the file now

I use the file for decisions that a capable engineer could reasonably infer in more than one way.

I write down ownership: whether the client or server is authoritative, which package owns a domain type, and where authentication is actually enforced. I write down boundaries that are easy to cross from inside one task, such as a generic package importing product-specific code or a client module pulling in a server-only dependency.

I also mark the parts that deserve unusual care. A prompt that has been tuned for months looks like a text file to an agent doing cleanup. A generated migration looks editable unless the repository says the human owns migrations. A released API may have consumers that are not visible in the current checkout.

The file includes the working routine too: which checks have to pass, which generated files should not be edited, and whether a browser build must be rebuilt before a change can actually be tested.

Some rules are there because I use several agents. An uncommitted worktree is not a reliable list of one agent's changes, so agents track the files they touched and stage them explicitly.

I leave out temporary plans and style rules that are already enforced by formatters, linters, or the type system. A long instruction file becomes another piece of stale documentation. If a rule is obvious from the code or can be enforced mechanically, it probably does not need a paragraph.

Types and tests still do most of the hard enforcement. The instruction file handles the decisions they do not express well. A type can describe the shape of synced data; it cannot say that an empty response must clear the local mirror. A test can pin that behavior once we know about it; the repository rule tells an agent to preserve the same authority model while building the next sync feature.

CLAUDE.md and AGENTS.md

Different tools look for different instruction filenames. I used to duplicate the contents, which created the predictable problem: one file received a new rule and the other did not.

I keep the detailed conventions in CLAUDE.md and make AGENTS.md a short direction to the right file. At the root of a monorepo, it says:

Before working in a package, read that package's CLAUDE.md. Each package has its own conventions and rules.

Inside a package, or in a repository with one application, it is even shorter:

Read CLAUDE.md for this project's conventions and rules.

That is all I need the pointer file to do.

Why I still bother with it

Stronger models make the setup feel less necessary because they can complete an isolated task with very little explanation. They can also change more of the system in one pass. When an assumption is wrong, it travels further before someone notices.

For client work, speed is useful when the result is still a system another engineer can maintain. I want agents to shorten implementation time without quietly choosing new data owners, bypassing an existing boundary, or reopening product decisions in every session.

I published a sanitized version of the CLAUDE.md from the recruiter-facing frontend package of Airbase in Agent Guardrails. It is a React application built with Vite, TanStack Router, and TanStack Query. The file contains the rules its coding agents actually use for content, data-fetching, routing, types, UI states, state management, prompts, and deployment configuration. I removed internal names and private implementation details.

I now update the file in the same change that fixes the bug or settles the design. The next agent session can then start with that decision already in the repository, instead of relying on me to remember to explain it again.

Written by Shahbaz Manzoor.

I build production AI agents for a living. if you're building one.