How to Actually Write a CLAUDE.md — A Solo Indie Dev's Guide From Running 16 Apps
Five battle-tested patterns from running 16 iOS apps and a couple of SaaS products solo for a year. Length caps from Karpathy and HumanLayer, scope separation, and the 'forbid + alternative' format.
On this page (18)
- What CLAUDE.md actually is
- How it differs from other config files
- How to split global vs project
- Pattern 1 — Pin identity and stack on the first line
- Pattern 2 — Pin tone rules with detail
- Pattern 3 — Write coding rules as "prohibition + alternative"
- Pattern 4 — Write workflow as steps
- Web (Next.js + Supabase)
- iOS (Xcode + Swift)
- Pattern 5 — Pin dangers explicitly
- Use @import to break up long rules
- Extra rules
- A small script to keep CLAUDE.md updated
- Limitations — what pinning can't fix
- What to pin first, by situation
- FAQ
- Wrapping up
- Sources
Every time I opened a new session, I was repeating the same things. "Reply in Korean." "Use the App Router, not Pages Router." "Explain in one line before throwing code at me." I'm running 16 apps as a solo indie, and every session felt like a new-hire interview.
I put up with this for a month, then drew a conclusion. Stop teaching inside the session — pin it outside the session. That's what CLAUDE.md does. It's an internal wiki you hand to the AI.
This is my guide to writing CLAUDE.md, distilled from a year of running it across 16 iOS apps and SaaS projects as a one-person operation. Bottom line up front: written well, it eliminates 30 minutes of context-setting per session. Written poorly, it gets ignored. Here are the patterns that made the difference.
> TL;DR
> - CLAUDE.md is a markdown file Claude Code auto-injects at session start. It's read from two locations: global and project.
> - A well-written 80-line page kills 30 minutes of context-setting per session.
> - The 5 core patterns: identity / tone / prohibitions / workflow / danger. Split into five zones.
> - "Use App Router" is weak. "No Pages Router → use App Router" is strong. Pin prohibition + alternative on one line.
> - Cap length at 60–300 lines. Karpathy's public CLAUDE.md is 65 lines. Past that, priorities collapse.
> - Don't mix rules with different scopes (Web vs iOS) in one global file. Split by project, or label the scope explicitly.
> - Use @import to break up long rule files. Past 200 lines in one file, the model loses priority signal.
What CLAUDE.md actually is
CLAUDE.md is a markdown file Anthropic's Claude Code auto-injects when work starts. It's read from two locations.
~/.claude/CLAUDE.md is the global rules file. It applies to every project. This is where I pin my identity, my tone preferences, and my safety rules. <project-root>/CLAUDE.md is the project rules file. It only applies inside that folder. This is where I pin codebase structure, conventions, and domain knowledge.
I use both. Identity and tone go global. Codebase-specific structure and risks go in the project file. Combined, every session starts already knowing me.
The analogy: instead of giving every new hire a fresh company tour, you hand them a wiki link. If the wiki is good, they're productive on day one. If it's not, they keep walking over to the founder's desk to ask questions.
How it differs from other config files
Several similar files exist. Cursor's .cursorrules, the newer .cursor/rules/*.mdc, Google Gemini CLI's GEMINI.md, OpenAI Codex's AGENTS.md. All markdown, but the injection mechanics differ subtly.
| File | Tool | Location | How it applies |
|------|------|----------|----------------|
| CLAUDE.md | Claude Code | global + project | auto-injected at session start |
| .cursorrules | Cursor (legacy) | project root | injected as system prompt every chat |
| .cursor/rules/*.mdc | Cursor (new) | folder inside project | conditional injection via globs |
| AGENTS.md | Codex CLI etc. | project root | injected at session start |
| GEMINI.md | Gemini CLI | global + project | nearly identical to CLAUDE.md |
The two I use most are CLAUDE.md and .cursor/rules/*.mdc. I do inline edits in Cursor and run bigger jobs in Claude Code, so the same rules end up scattered across two files. I covered how to keep that in sync separately in Cursor Rules vs CLAUDE.md compared.
The short version: CLAUDE.md is always in. .cursor/rules/*.mdc can be conditionally injected via globs. So the token cost of CLAUDE.md is higher. But it's simpler — one file to look at.
How to split global vs project
My rule is simple. If it doesn't change with the human, global. If it changes per codebase, project.
Global gets the fact that I'm a solo dev, the "reply in Korean" tone, and the "never touch .env" safety rule. These are the same whether I'm building a SaaS or an iOS app. Project gets that codebase's folder structure, conventions, and domain vocabulary. The MVVM layout of a SwiftUI project and the App Router layout of a Next.js project have no reason to live in the same file.
| Pin zone | Global | Project |
|----------|--------|---------|
| My identity | O | X |
| Reply language / tone | O | X |
| Safety / prohibition rules | O | reinforce |
| High-level stack | O | X |
| Folder structure | X | O |
| Domain vocabulary | X | O |
| Frequently used npm scripts | X | O |
Without this split, the global file bloats. My global is 80 lines and I never let it grow past that. There was a phase where it ballooned to 200 lines, and at some point the model stopped picking up priorities. I cut it back to 80 and rule adherence climbed again.
For reference, Andrej Karpathy's public CLAUDE.md is 65 lines. Operations-heavy companies like HumanLayer recommend keeping global under 60 lines and splitting overflow into agent_docs/. 80–300 lines is a safe ceiling.
One more thing. Don't mix differently-scoped rules in one global file. I keep only my reply language and the "solo indie" identity in global. Web code conventions and iOS code conventions both moved into their respective project CLAUDE.md files. If both stacks' rules sit in the global file, iOS rules get pinned into the context even during Web work, and the model gets confused. If you really must keep both global, label scope explicitly with something like "for Web only".
Pattern 1 — Pin identity and stack on the first line
The first line should say who you are and what you use. The most common AI question is "what stack are you on?" Answer it ahead of time and it doesn't ask.
My first line: "Solo indie dev (iOS + Web SaaS)". That single line locks in a lot of assumptions. Say "build me a sign-up flow" and Claude automatically assumes a one-person operation. It doesn't recommend pricey services like Auth0. Since I also do iOS, it remembers mobile auth tokens too.
For the stack, a noun list works best. "Supabase, Vercel, Tailwind." Now PostgreSQL, edge functions, and App Router are all default assumptions. It stops asking "is this Firebase?".
One caveat. Don't pin versions. If you write "Next.js 16", you'll forget to update it when 17 ships. Just write "Next.js" and let the model assume current.
Pattern 2 — Pin tone rules with detail
This is where I got the most leverage. Everyone writes "reply in Korean", but what comes after that matters more.
One important distinction. Pinning tone is not the same as pinning code style. Indentation, semicolons, quote style — that's ESLint and Prettier territory, not CLAUDE.md territory. Telling an LLM to enforce linter rules just burns tokens with weak results. Tone-pinning should stay limited to actually-human stuff: reply language, first-person, greeting patterns, banned words.
I pinned "Keep explanations concise, skip unnecessary preamble." That one line killed translated-from-English greetings like "Of course! Great question." I also pinned "Explain in one line before changing code." So instead of dumping code, it now writes "I'm going to extract this function — for reuse" first.
For blog writing it gets more granular. In the project CLAUDE.md, I pinned "Use the 했다체 (declarative) sentence form", "Sentences under 40 characters", and a list of banned words. This article is being written under those rules right now. Tone responds to detail. "Be friendly" is vague, but "no emojis" is concrete.
Tone is the easy thing to forget. Code violations are obvious; tone violations sneak past you for a while. That's exactly why pinning matters.
Pattern 3 — Write coding rules as "prohibition + alternative"
This one I learned the hard way. "Use the App Router" is weak. "No Pages Router" is stronger. But the truly strong form combines both. "No Pages Router → use App Router", on a single line.
There's a reason the difference is large. Training data has way more Pages Router examples than App Router. If you only say "use App Router", the model defaults to majority-class Pages Router under pressure. If you pin "No Pages Router", that phrase enters the context and the model token-by-token avoids it. But pure negative rules aren't enough — sometimes the model halts at "okay then what" and slips into a different legacy pattern. So always attach the alternative right after the prohibition. Anthropic's official guidance also recommends "constructive rules over purely negative ones" for consistency.
My global has five negative + alternative rules: "No Pages Router → App Router", "No class components → functional components", "No completion handlers → async/await" (iOS), "No git push --force", and "Minimize separate CSS files → Tailwind utilities first". Each one was added after I got bitten.
Negative rules also protect me from myself. At 3 a.m., Pages Router can start to look faster. Claude steps in: "this is Pages Router, but CLAUDE.md prohibits it." It's a pin for future me.
Pattern 4 — Write workflow as steps
"Plan → approve → implement → verify." I pinned that 4-step flow globally. Without it, "build me a sign-up flow" will get you 5 files dumped at once.
My pinned phrasing: "For complex tasks, present a plan before coding" and "Wait for plan approval before starting implementation." With those two lines, Claude now writes "1) add the route, 2) add the Supabase table, 3) UI. Should I proceed?" first. If I reply "just step 1 for now", it does only step 1.
Verification is pinned too. "After writing code, run the build/typecheck if possible." That single line means it doesn't drop code and walk away — it builds its own code, sees the type error, and fixes it.
I also pin the commands I use frequently. The end of my global has a block like this.
```markdown
Commands I use
Web (Next.js + Supabase)
npm run dev # dev server (localhost:3000)
npm run build # production build
npm run typecheck # TypeScript type check
npx supabase db push # apply Supabase migration
iOS (Xcode + Swift)
xcodebuild -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15' build
```
With this pinned, Claude doesn't guess which npm script to run. Tell it "type-check this" and it just runs npm run typecheck.
The point is to pre-authorize what the AI can do autonomously. When permissions are vague, it goes conservative and keeps asking. Pinned permissions, it just goes.
Pattern 5 — Pin dangers explicitly
The last pattern matters most. Pin the things the AI must never do, separately.
I have three danger pins. "Never read or modify .env files", "Always ask before modifying production files", "No git push --force". These are the worst-case patterns for a solo dev.
The .env rule was added after I got burned directly. Mid-debug, Claude printed a chunk of .env and it ended up in the session log. It was a dev key, thankfully — if it had been production I'd have had to rotate everything. Pinned it globally the next day.
The "ask before production" rule was added after I almost shipped a production migration via a two-line PR. Solo operations often skip staging. "Obviously dangerous" isn't enough — you need to pin "this is dangerous" explicitly.
Use @import to break up long rules
When rules cross 80 lines, I switch to @import. Claude Code supports this officially. Write @docs/conventions.md inside CLAUDE.md and it pulls that file in and merges it.
My global stays short, but my project CLAUDE.md files use imports. Like this.
```markdown
Project conventions
@docs/db-schema.md
@docs/api-conventions.md
@docs/danger-rules.md
Extra rules
- Billing-related code can only be modified inside
lib/billing/ - Supabase RLS policies must only change via migrations
```
Two upsides. First, splitting rules by topic looks better on GitHub. Second, when multiple projects share rules, I only edit one file. My two SaaS projects both import docs/danger-rules.md.
The downside: imported files still hit the context. This isn't a token-saving feature. Use it for organization only.
A small script to keep CLAUDE.md updated
One more pattern I use. At the end of a session, I tell Claude "if you learned a new rule today, add it to global." But I forget to type that every time, so I keep a tiny helper around.
```bash
#!/usr/bin/env bash
scripts/append-claude-rule.sh — safely append a new rule to ~/.claude/CLAUDE.md
set -euo pipefail
CLAUDE_MD="${HOME}/.claude/CLAUDE.md"
TODAY=$(date +%Y-%m-%d)
if [ -z "${1:-}" ]; then
echo "Usage: ./append-claude-rule.sh \"new rule on one line\""
exit 1
fi
Auto-backup, then append
cp "$CLAUDE_MD" "${CLAUDE_MD}.bak.${TODAY}"
printf "\n- %s # added %s\n" "$1" "$TODAY" >> "$CLAUDE_MD"
echo "added → $CLAUDE_MD"
echo "backup → ${CLAUDE_MD}.bak.${TODAY}"
```
No more opening vim, and the auto-backup means if I delete something accidentally I can restore it. I have it as a zsh alias, so I run cmd-rule "no Pages Router" and it's done.
There's a longer story to be told about this kind of automation, but the core idea is: lower the friction of pinning. The closer friction gets to zero, the more pins accumulate. Add friction and you'll get lazy.
Limitations — what pinning can't fix
CLAUDE.md isn't magic. After a year of running it, I'll cop to three real limitations.
First, long contexts start to override it. If you code for hours in one session, the accumulated context outweighs CLAUDE.md. Late in the session it might suddenly answer in English or suggest Pages Router. When this happens, just open a new session. Don't drag it out.
Second, write too much and the details get drowned. My global is 80 lines. Early on it ballooned to 200 and the longer the rules got, the worse the model's prioritization became. I get it — even human employees don't follow 200 rules.
Third, if I break my own rule, the AI won't stop me. If I explicitly say "do this with Pages Router", Claude does it. Pinning sets defaults, not guardrails on the human. At the end of the day, controlling myself is still my job.
What to pin first, by situation
When you're first writing this thing, it's hard to know where to start. From my experience, the priority order is simple.
| Situation | First pin | Why |
|-----------|-----------|-----|
| Writing CLAUDE.md for the first time | reply language + identity | effect lands on the very first response |
| You've nagged the same thing 3+ times | that nag itself | you already know it's a rule |
| Right after an incident | danger rules | pin it or it happens again |
| Just joined a project | folder structure | stop file-location guessing |
| Blog/docs writing | tone + banned words | output tone stabilizes immediately |
This single table is a year's worth of trial-and-error. The two highest-leverage pins were always the top two: "reply in Korean" and "the rule I've now repeated three times". The latter is the strongest one.
FAQ
If I write a CLAUDE.md, does Cursor follow those rules too?
No. Cursor doesn't read CLAUDE.md. Cursor only reads .cursorrules or .cursor/rules/*.mdc. If you use both tools, you have to pin the same rules in both files. I copy only the core rules to both sides and keep the details in each tool's native format.
Does writing a long CLAUDE.md eat a lot of tokens?
Yes. It's injected into the system prompt at every session start. 80 lines is roughly 400–500 tokens. Not a heavy load, but if it bloats to 1000 lines, the per-session cost adds up. That's why I cap mine at 80.
Do imported files also count as tokens?
Yes. @import just merges and sends. It's not a token-saving trick, only an organizational one. For real token savings, you want conditional injection like Cursor's globs.
Won't global and project conflict if I use both?
If they conflict, project wins. Global says "reply in Korean", but if a specific project pins "reply in English for this project", that project replies in English. The more specific, closer rule wins.
Is it worth creating an empty CLAUDE.md for a project?
Yes. Even one line is enough. "This project is Next.js + Supabase, Stripe for payments." That single line changes the first response. Without it, the model guesses from the folder layout and guesses wrong often.
Should I commit CLAUDE.md to git?
Project CLAUDE.md, yes. Especially if you have collaborators. Global ~/.claude/CLAUDE.md holds my identity and tone, so I keep it in a dotfiles repo separately. The two files have different shapes.
Does CLAUDE.md apply to claude.ai web/mobile?
No. CLAUDE.md is Claude Code only. claude.ai web and the mobile app use the "Project instructions" feature inside Projects for a similar role. But it doesn't auto-read the same file. You have to copy the text in.
Wrapping up
CLAUDE.md takes 5 minutes to start. 3 lines of identity, 3 lines of tone, 5 lines of coding rules, 4 lines of workflow, 3 lines of danger. An 18-line v1 is enough. Add what bites you over the next month. A year in, 80% of how you work will be pinned.
One tip when you write the first version. Don't just freestyle it — do a quick retrospective first. When I wrote my v1, I first listed "the 5 things I nagged Claude about most this past week." That list became my global rules verbatim. Whatever I keep telling it is exactly what should be pinned first.
CLAUDE.md is the wiki you hand to the AI. Written well, it works like a coworker from day one. Written poorly, you remain founder, new hire, and everything in between. Running 16 apps as a solo, the latter doesn't scale. Better to start today.
Sources
- Anthropic — Claude Code Memory and CLAUDE.md
- Anthropic — Claude Code Settings
- Cursor — Rules for AI
- Google — Gemini CLI Configuration
- OpenAI — Codex CLI AGENTS.md spec
- Supabase — Database Schema Best Practices
> This article reflects information as of 2026-04-30. Claude Code and the memory behaviors of each tool may change after this date.
Related Posts
Claude Code vs Cursor vs Windsurf — A Solo Indie Dev Used All Three for a Month
I paid for and used Claude Code, Cursor, and Windsurf for over a month each. Autocomplete, agent mode, MCP, debugging, and real coding speed — compared from a solo indie developer's perspective.
Cursor Rules vs CLAUDE.md — A Deep Dive into Context Injection Patterns for AI Coding Tools
How Cursor Rules and CLAUDE.md actually differ under the hood, written by a solo dev running both in the same project. Covers Cursor's 4 modes, CLAUDE.md's 5 locations, and the AGENTS.md single-source-of-truth workflow.
The Claude Code Skill & Plugin Set I Actually Use — Mapped by Dev Task
Four plugins (superpowers, vercel, frontend-design, bkit) cover seven dev tasks — UI, backend, data, deploy, planning, review, docs. Includes exact Skills per task, feature flow, and a mapping onto the EP.19 5-agent team.