What actually belongs in CLAUDE.md — and what to move to skills, hooks, or docs

작성자

카테고리:

← 피드로
DEV Community · Rulestack · 2026-07-30 개발(SW)

Rulestack

Your CLAUDE.md only grows. Every incident adds a rule, every preference becomes a paragraph, and six months in you have a file that reads like a wiki — and an agent that follows maybe half of it.

The uncomfortable mechanics: CLAUDE.md is loaded into context automatically, which means every line is a tax on every single request, whether it’s relevant or not. And instructions are advisory — the more of them there are, the weaker each one pulls. I’ve watched my own rules drift off quietly in long sessions and only caught it in the diff.

So the real question isn’t “what should I write in CLAUDE.md?” It’s “what has earned a permanent seat in context — and where does everything else live?”

Claude Code gives you four places to put guidance, and they have very different cost models:

Destination Loaded Costs tokens Enforcement CLAUDE.md every session, automatically always advisory Skill body only when the task matches on demand advisory, but scoped Hook never enters context as prose ~zero deterministic docs/ file when the agent (or a rule) points at it on demand none — it’s reference

Here’s the sorting test I use, one sentence per destination.

The one-sentence test

Keep it in CLAUDE.md if it’s true on every turn, cheap to state, and expensive to violate. Build commands, naming conventions, “tests live in test/, mirrored by path”, the tone your commit messages use. If you’d want the rule active even in the middle of an unrelated refactor, it’s an invariant — that’s what the always-loaded file is for.

Move it to a skill if it starts with “when doing X”. Deploy checklists, a review rubric, the release-notes format, your migration playbook. A skill’s name and description stay visible so the agent knows it exists, but the body only loads when the task actually matches. That’s exactly the deal you want for situational expertise: discoverable, but not billed on every request. (Format details: how to write a SKILL.md that actually triggers.)

Move it to a hook if the sentence contains “never” or “always” and a machine could check it. Formatting before commit, protected paths, “don’t touch .env“, “block pushes to main”. Don’t ask a language model to remember what a shell script can enforce — a hook fires deterministically whether the context is fresh or fifty files deep. (Hook config structure and matchers.)

Move it to a docs file if it’s something the agent should read, not obey. Architecture overviews, API quirks, the history of why the billing module is weird. Put it in docs/, and leave one line in CLAUDE.md saying when to read it: “Touching billing? Read docs/billing-history.md first.” Reference material rents context only when it’s relevant.

Why the default answer is “not CLAUDE.md”

Three costs stack up on the always-loaded path:

  1. You pay it every request. A 4,000-token CLAUDE.md is 4,000 tokens on every prompt, every tool call round-trip, all day. I ran this audit on my own setup recently: half the file only mattered on release days. Moving that half out cut every request’s overhead and nothing broke.
  2. Attention dilutes. Twenty rules each get a sliver of the model’s compliance budget. Five rules get real weight. This isn’t a documented parameter — it’s the consistent shape of what I see in practice: lean files hold, bloated ones get sampled like suggestions.
  3. Long sessions erode prose. As context fills with diffs and tool output, early instructions fade — and after compaction, what survives is a summary, not your exact wording. (What survives compaction, and how to keep rules alive.)

Hooks dodge all three. Skills dodge the first two. Docs dodge all three but enforce nothing. CLAUDE.md is the only slot that pays full price — spend it on the few rules that deserve it.

What a lean CLAUDE.md looks like

# myproject — agent guide

## Commands
- Build: `pnpm build` / Test: `pnpm test` (single file: `pnpm test path/to.test.ts`)
- Never run `pnpm deploy` directly — use the deploy skill.

## Conventions (always)
- TypeScript strict; no `any`. Errors are returned, not thrown, in `src/core`.
- Tests mirror source paths. New modules need a test file in the same PR.
- Commit messages: imperative mood, reference the issue.

## Enforced by hooks (listed so you know they exist)
- Format-on-edit, protected paths (`.env`, `migrations/`), test gate before commit.

## Where things live
- Architecture: `docs/architecture.md` (read before cross-module changes)
- Billing quirks: `docs/billing-history.md` (read before touching `src/billing`)

## When unsure
- Prefer asking over guessing on anything in `migrations/`.

Enter fullscreen mode Exit fullscreen mode

That’s the whole shape: commands, invariants, a pointer to what’s machine-enforced, a map, and an escalation rule. One screen. The “enforced by hooks” section is deliberately redundant — the hook does the enforcing, the mention just stops the agent from being surprised.

Migrating a bloated file in ~20 minutes

  1. Measure it. Paste your CLAUDE.md into a token counter. Write the number down — this is your before.
  2. Label every block with one of four words: always (true every turn), situational (“when doing X”), never-event (machine-checkable prohibition), reference (explains, doesn’t instruct).
  3. Move everything that isn’t always. Situational → .claude/skills/<name>/SKILL.md with a description that names the trigger. Never-events → hooks or CI checks. Reference → docs/, with a one-line pointer left behind.
  4. Add a canary. One harmless, visible rule — “start responses touching src/billing with the word BILLING” — so you can tell at a glance whether the file is being honored at all. (More ways to verify what actually ran.)
  5. Re-measure and re-audit monthly. The file will try to grow back. When a new rule wants in, make it pass the one-sentence test first — most candidates are situational or machine-checkable, and belong downstream.

One honest caveat: none of this makes prose rules reliable. Advisory is advisory. What the sort gives you is a small enough always-loaded core that the model can actually hold it, plus deterministic enforcement for the things that genuinely can’t be violated. If a rule keeps breaking after all this, that’s your signal it wanted to be a hook all along.

If you’re not sure how your rules files get read in the first place — CLAUDE.md, AGENTS.md, and Cursor’s rules all load differently — start with how each tool actually loads your project rules.

I’m Rulestack — I build and maintain drop-in rule, skill, and hook packs for Claude Code, Cursor, and Codex, at rulestack.gumroad.com. I post working AI-coding tips on Bluesky at @ai-shop.bsky.social — follow along if this was useful.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다