CLAUDE.md rots because it holds the plan

Most coding agents read a file at the root of the repository before doing anything else: CLAUDE.md for Claude Code, AGENTS.md for OpenCode and others, .cursorrules for Cursor, .github/copilot-instructions.md for Copilot. The file is loaded into every session and the agent treats it as ground truth. That is the point of it, and also the problem: the file is static, the project is not, and nothing forces the two to agree.
The failure mode
Here is a CLAUDE.md fragment of the kind that is easy to write in March:
## Current work
We are migrating the API from Express to Fastify. New routes go in
`src/fastify/`. Do not add anything to `src/express/`.
Run tests with `npm test`.
By June the migration is finished, src/express/ is gone, src/fastify/ has been renamed to src/http/, and tests run with pnpm vitest. Nobody edits the file, because nobody reads it; it is for the agent. In August a developer asks the agent to add an endpoint. It creates src/fastify/ again, writes a route there, runs npm test, gets a missing-script error, and reports that the test runner is broken. Each step was correct given the instructions. The instructions were four months old.
The rot is worst in the lines that felt most useful when written: what is being built now, what is off-limits this week, which branch is the integration branch. Their half-life is measured in sprints.
What a static file is good at
The file is loaded every time and travels with the code through branches, clones and forks, so it is the right place for things that change at the same rate as the repository:
- Commands. Install, build, test, lint, run one test in isolation. They change rarely and are painful to guess.
- Conventions the code does not make obvious. Where new modules go, which error type to throw, why a directory is named the way it is.
- Gotchas. Whatever cost a human an afternoon once: a framework version newer than the model's training data, a script that must not run twice, a generated directory that must not be edited by hand.
In the Autoplans repository, packages/web/CLAUDE.md is one line, @AGENTS.md, and the AGENTS.md it imports is nine lines: one gotcha (the installed Next.js is newer than the model's training data; read node_modules/next/dist/docs/ first), written by next dev itself, so the tool that knows when it changes is the one that regenerates it.
What it is bad at
Anything with a status. "We are currently", "this sprint", "do not touch X until Y lands", a list of tasks with checkboxes: all of it describes a moment, and the file has no way to know the moment has passed. The agent cannot tell a live instruction from a dead one; both are plain text in the same file with the same authority.
The usual fix is discipline. It does not hold, for the same reason README files go stale: updating documentation is never the change you are making, it is a chore attached to it.
The split
The fix has two halves.
Keep the instruction file small and about conventions. If a line would be wrong after the next sprint, it does not belong there; if a new team member could not act on it in six months without asking anyone, take it out. The Autoplans VS Code extension's Copilot generator (last section) asks its model for 20–50 lines and "only discoverable patterns, not aspirational practices", which is a fair target for a hand-written file too.
Keep the plan somewhere that is updated as work happens, and give the agent a way to read it at the start of every session. The plan (what is being built now, what is blocked, what finished yesterday) has to live in a system whose state changes when the work changes, not in a file that changes when someone remembers: an issue tracker with an API, a task board queried over MCP, or a file a tool regenerates from the tracker. The agent's picture of "now" should be fetched, not remembered.
An example CLAUDE.md
Short, all conventions, nothing with a date on it, and one line telling the agent where the plan is:
# acme-shop
pnpm monorepo. `pnpm install` at the root; never run npm.
## Commands
- Build: `pnpm -r build`
- Test everything: `pnpm test`
- One package: `pnpm --filter @acme/api test`
- One file: `pnpm --filter @acme/api vitest src/orders/total.test.ts`
- Lint: `pnpm lint` (runs in CI; fix the code, do not disable rules)
## Layout
- `packages/api` — HTTP server. Routes in `src/http/`, one file per resource.
- `packages/shared` — Prisma schema and types. Migrations live here, not in api.
- `packages/web` — Next.js app. Server components by default.
## Conventions
- Throw `AppError` from `@acme/shared/errors`; never throw strings.
- Prisma calls go through a repository in `src/repos/`, not route handlers.
- Match the surrounding file's style before adding a new pattern.
## Gotchas
- `packages/web/src/generated/` is built output. Edit `packages/shared`.
- The installed Next.js is newer than your training data; read
`node_modules/next/dist/docs/` before touching routing.
## Current work
Not here. Read `.autoplans/project.json` and call `list_tasks` with its
`projectId` before starting anything.
Around thirty lines. Only the last section points at something that changes.
When to regenerate
- When a command in the file stops working. That is the trigger, not a calendar.
- When a directory in the Layout section moves. Grep the file for the old path in the same pull request that moves it.
- When you have corrected the agent on the same convention twice. Write it down the second time.
- Not at the start of every sprint, and not with the sprint's contents. If you feel the urge to write "we are currently", put it in the tracker instead.
Where the plan lives instead
The tracker needs an address the agent can find from inside the repository. In Autoplans that is .autoplans/project.json, created from the desktop app's projects panel or written by hand (see Projects):
{
"projectId": "1fb3fe7d-e3f9-4992-ae18-29b2bade4bd4",
"name": "My project",
"url": "https://autoplans.dev/dashboard/projects/1fb3fe7d-...",
"linkedAt": "2026-08-26T10:00:00.000Z"
}
It is the one thing in .autoplans/ that does not rot, because it holds an id rather than state. The desktop app and CLI's bundled instructions tell every agent to read it first and call autoplans_project_tasks with that id rather than listing projects, and the VS Code extension reads it for Show Task Map. The Claude Code plugin's README describes the marker, but its /start and /finish commands do not read it; they find the task by listing.
The plugin's workflow is the part worth copying. /start calls code_agent_get_task_with_dependencies, stops if the result reports hasUncompletedDependencies, and sets the task in_progress before any code is written. /finish posts a summary of the diff as a comment, then sets completed if the work is verified and waiting_for_review if not; the desktop and CLI agents follow the same rule (see Agents).
Any MCP client can reach the same tasks through the server at https://autoplans.dev/api/v1/mcp; the check-then-set-in_progress sequence above is the plugin's prompt, so a bare client needs the equivalent line in its own instruction file:
{
"mcpServers": {
"autoplans": {
"type": "http",
"url": "https://autoplans.dev/api/v1/mcp",
"headers": { "Authorization": "Bearer apk_live_..." }
}
}
}
Against the server itself the call is list_tasks with the projectId from project.json and an optional status, which is what the example file asks for. autoplans_project_tasks is the desktop, CLI and OpenCode plugin's wrapper around that tool; code_agent_* are server tools in their own right. Per-client blocks are on the MCP server page; the tool list is under MCP tools. If you are not using Autoplans, the same shape works with any tracker that has an API.
If you use the Autoplans VS Code extension
The extension has three commands that touch these files, one of them named Sync Project to Repository. "Sync" can mean many things, so here is exactly what each one reads and writes, taken from packages/vscode-extension/src/commands/syncCommands.ts at 0.1.14.
Autoplans: Generate Copilot Configuration
The command is also exposed to Copilot agent mode as the language-model tool autoplans_generate_copilot_config. It:
- Resolves the project: it reads
package.json, the first heading and paragraph ofREADME.mdand of.autoplans/README.md, then lists your projects over MCP and matches by name. No match means the first project on the account; with no API key set, it uses the detected name and a local placeholder id. - Asks VS Code for a Copilot model (
vscode.lm.selectChatModels({ family: 'gpt-4o' })). With one, it reads the first 2,000 characters of each of.github/copilot-instructions.md,AGENT.md,AGENTS.md,CLAUDE.md,.cursorrules,.windsurfrules,.clinerulesandREADME.mdthat exists and asks the model to merge them into a 20–50 line file, keeping what is still valid and adding a "Task Management with Autoplans" section listing the extension's ownautoplans_*language-model tools. Without a model, or on error, it writes a fixed template: placeholder sections plus a longer Autoplans section that also lists the business-plan tools and enumerates task types and statuses. - Writes
.github/copilot-instructions.mdand.github/chatmodes/Autoplanner.chatmode.mdwithfs.writeFileSync: an overwrite, with no diff, prompt or backup. The merge happens in the model's answer, not in the write. The chat-mode file is a fixed template with the project name and description substituted in.
It never writes CLAUDE.md, AGENTS.md or .cursorrules; those are inputs only, folded into the Copilot file and left alone.
Autoplans: Initialize .autoplans Folder
Tool name autoplans_initialize_autoplans_folder. Same project resolution, then, if it resolved a real project, it fetches that project's tasks over MCP and writes under .autoplans/:
| Path | Contents |
|---|---|
README.md | Name, description, links to the two files below, and a note that manual edits may be overwritten |
tasks.md | Every task as a heading with status, id, priority, type and description, plus a Last updated ISO timestamp |
architecture.md | An empty template with three headings to fill in |
tasks/ | Created empty |
All three files are overwritten on every run. tasks.md is a snapshot, exactly as stale as its timestamp: useful for a reviewer skimming the repository, not what an agent should read for the current plan.
Autoplans: Sync Project to Repository
Tool name autoplans_sync_project_to_repo. A picker offers three options. "Create Locally" runs the two commands above in sequence. "Push to GitHub" and "Both" are labelled Coming Soon: the first only shows a message; "Both" creates the files locally and then shows the same message. Nothing is pushed either way; the files land in your working tree and you commit them.
Two things the extension does not do
None of these commands writes .autoplans/project.json; the extension only reads it. And the language-model tool wrappers report files they did not write: autoplans_generate_copilot_config names .github/chatmodes/autoplanner.md (the file is Autoplanner.chatmode.md) and autoplans_initialize_autoplans_folder names .autoplans/progress.md, which does not exist. Check the working tree, not the tool's answer.
If you use the Copilot generator, treat its output as a draft to trim: the prompt asks for 20–50 lines, the fallback template is longer and full of placeholders, and every run overwrites the last. Commit the result so the next run shows up as a diff.
Tags
Ready to Transform Your Development Workflow?
Join developers who are building faster with AI-powered project management.
Get Started Free