CLAUDE.md Is Your Project's Runbook
By day three, every new conversation started with 8–10 minutes of preamble: front end is Next.js, back end is FastAPI, database is on Neon, auth is Clerk, deploys go to Vercel and Fly, don't merge from the command line because once it clashed with a GitHub-UI merge, don't run migrations in the app's startup because there's a whole story…
The fix: put the preamble in a file Claude reads automatically.
What the file is
CLAUDE.md lives at the root of the project alongside the code. Claude Code reads it at the start of every conversation. It's just Markdown. The most useful kind is a project runbook — what someone new would need to know to help without blundering.
Sections in mine:
- Stack. One line per piece. "Frontend: Next.js 16, deployed on Vercel, auto-deploys from main."
- Directory layout. Just what isn't obvious. "
services/holds scoring logic.routers/is API endpoints.models/is one file per database table." - Non-obvious conventions. The section with teeth. "Migrations run from the Dockerfile, not the app's startup — there's a story, see the memory file." "Frontend still uses
middleware.tseven though Next 16 warns about it, because Clerk hasn't caught up. Ignore the warning." - How to work with this repo. Push branches, merge in the GitHub UI, then pull. Never CLI-merge. The deploy command. Where env vars live.
- Working with me. Personal preferences. "Walk me through the why of git commands; I'm new to them. Prefer recommendations with tradeoffs over menus. Ask before non-trivial work."
About 150 lines of Markdown. ~40 minutes to write the first version, drawing from memory plus a pass through the codebase to make sure descriptions matched reality.
What changed
Morning after committing the file, opened a new conversation: "Let's look at the Study mode bug."
Claude's first paragraph: "Looking at frontend/app/study/[songId]/page.tsx and the related hook — before I dig in, the CLAUDE.md says to boot uvicorn locally before merging startup-shaped changes. This isn't a startup change, so I'll skip that, but want to flag it since we're likely going to touch state management."
Claude had read the file, filed the context, and proactively connected the current request to things written down days earlier. Treating the file the way a new teammate would treat a good onboarding doc — but with no forgetting.
Every conversation since has started from the same baseline. No re-explaining the stack, the deploy flow, or the migration rule.
What makes a runbook useful
A few things I've noticed after living with it for a few days:
Write what's non-obvious, not what's obvious. The file doesn't need to say "this is a web app." It needs to say "this is a web app, AND the front end can't read the backend's environment variables because they're on separate deploys, AND you have to set them in Vercel's dashboard separately, AND forgetting this has burned me twice." Friction points are the content; obvious stuff is noise.
Include things that would trip up a careful person. Mine has: "local dev points DATABASE_URL at the prod Neon database; there is no SQLite fallback; be careful when running migrations locally." Without that line, a careful person would reasonably assume running a migration locally is a safe isolated experiment.
Write in the voice of "things I've been burned by." Tone is mildly war-story. Not dramatic, just acknowledging that each item earned its place. "The Dockerfile runs migrations before uvicorn because we had a deadlock once — don't move them." Each bullet has weight behind it.
Link to deeper docs. My CLAUDE.md names memory files and says what they're about. "See memory/feedback_migrations_outside_lifespan.md for the story." Claude opens that file if it needs detail. The runbook is an index, not an exhaustive doc.
Keep it current. Hard part. If the file says "we use Clerk dev tier keys" and I switch to prod keys without updating, the file is now actively misleading. I treat it as first-class: any decision that would surprise a newcomer goes into CLAUDE.md in the same PR as the decision.
CLAUDE.md vs memory files
CLAUDE.md lives inside the project and carries project-specific conventions. Memory files live outside the project and carry personal preferences and corrections.
The distinction matters because of audience. A new contributor inherits CLAUDE.md but not my memory files. That's correct — my preference for git explanations isn't something a contributor needs to adopt, but "migrations go in the Dockerfile, here's why" is something they need to adopt or they'll re-introduce the outage.
Writing both made the same observation visible: a lot of what I know about a project isn't in the code. Some of it is personal, some of it is project-specific. Neither can be inferred from reading the code. Both, written down, smooth every future conversation.
Takeaways
- CLAUDE.md is a project runbook. Lives in the repo. Holds project-specific stack, conventions, and workflow.
- Write what's non-obvious, not what's obvious. Friction points are the content; the rest is noise.
- Include things that would trip up a careful person. The careful person is the one who'd most reasonably make the wrong assumption.
- Treat CLAUDE.md as first-class. Update it in the same PR as any decision that would surprise a newcomer; otherwise it becomes actively misleading.
- CLAUDE.md (project) and memory files (personal) serve different audiences. Both belong in the workflow.
Terms
CLAUDE.md — A Markdown file at the root of a project that Claude Code reads automatically at the start of every conversation. Documents project-specific stack, conventions, and workflow.
Memory file — Plain text file Claude Code automatically reads on every new session. Lives outside the project repo. Records personal preferences and lessons that persist across all conversations on all projects.
Markdown — A lightweight formatting syntax for plain text — what this post is written in. **bold**, # headings, etc.
Next.js — The React-based framework the frontend is built with. "Next 16" is a specific version.
FastAPI — The Python web framework the backend is built with.
Neon — A hosted, serverless flavor of Postgres.
Clerk — An authentication service. Handles sign-in, sign-up, sessions, and user records.
Vercel — A platform that hosts frontends, especially Next.js apps. Auto-deploys when I push to GitHub.
Fly — Fly.io. Hosts backend services in containers.
Migration — A script that changes the database schema (adds a table, adds a column, etc).
Dockerfile — A text file describing how to build a container image.
Uvicorn — The web server that runs FastAPI.
middleware.ts / proxy.ts — A frontend file Next.js runs on every request before serving a page. In Next 16 the convention was renamed to proxy.ts but the Clerk library still expects the old name; the build prints a warning that we ignore.
Environment variable — A named value (like DATABASE_URL) the running process can read. Vercel and Fly each have their own dashboards for setting these; getting them in sync across both is a manual step.