CLAUDE.md vs. skills: where should Claude Code instructions live?
Put it in CLAUDE.md if Claude should know it in every session: build commands, conventions, architecture, "always do X" rules. Put it in a skill if it is reference material Claude needs occasionally, or a procedure you want to trigger by name. Put it in a path-scoped rule if it only applies to part of the codebase.
The distinction is loading behaviour, and loading behaviour is a cost. CLAUDE.md is in context for every request whether it is relevant or not. A skill costs a description until the moment it is used.
The three mechanisms
| CLAUDE.md | .claude/rules/ |
Skill | |
|---|---|---|---|
| Loads | Every session | Every session, or when matching files are opened | On demand, when invoked or judged relevant |
| Scope | Whole project | Can be scoped to file paths | Task-specific |
| Context cost | Full content, every request | Full content when loaded | Description every request; body only when used |
| Can be triggered by name | No | No | Yes, with /<name> |
| Best for | Core conventions and commands | Language- or directory-specific guidance | Reference material and repeatable workflows |
All three are context rather than enforced configuration. Claude treats them as instructions to
follow, not rules it cannot break. If something must hold every time — never edit .env, never push
to main — a PreToolUse hook is enforcement and a written instruction is a request.
What belongs in CLAUDE.md
Treat CLAUDE.md as the place you write down what you would otherwise re-explain. The documented triggers for adding to it are specific:
- Claude makes the same mistake a second time.
- A code review catches something Claude should have known about this codebase.
- You type the same correction you typed last session.
- A new teammate would need the same context to be productive.
That last one is the best test. If a new hire would need it on day one regardless of what they were working on, it is a CLAUDE.md fact.
Files can live at several scopes, loaded from broadest to most specific so a project instruction appears after a user instruction:
| Scope | Location | Shared with |
|---|---|---|
| Managed policy | /Library/Application Support/ClaudeCode/CLAUDE.md (macOS), /etc/claude-code/CLAUDE.md (Linux/WSL), C:\Program Files\ClaudeCode\CLAUDE.md (Windows) |
All users in the organisation |
| User | ~/.claude/CLAUDE.md |
Just you, all projects |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
The team, via source control |
| Local | ./CLAUDE.local.md |
Just you, this project — gitignore it |
Files in the directory hierarchy above the working directory load at launch; files in subdirectories load on demand when Claude reads files there. All discovered files are concatenated rather than overriding each other, ordered from the filesystem root down, so instructions closer to where you launched Claude are read last.
Run /context in a session and check Memory files to confirm what actually loaded. That is a more
reliable answer than reasoning about the resolution rules.
The 200-line rule, and what it is really about
Target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence — the second effect matters more than the first. A 600-line instruction file does not make Claude follow 600 lines of instruction; it dilutes the ones that mattered.
Four writing habits move the needle:
Be specific enough to verify. "Use 2-space indentation" instead of "format code properly." "Run
npm test before committing" instead of "test your changes." "API handlers live in
src/api/handlers/" instead of "keep files organised."
Structure it. Markdown headers and bullets. Claude scans structure the way readers do.
Remove contradictions. If two rules conflict, Claude may pick one arbitrarily. Review nested
CLAUDE.md files and .claude/rules/ periodically for rules that outlived their reason.
Leave notes for humans in HTML comments. Block-level <!-- ... --> comments are stripped before
the content enters context, so maintainer notes cost nothing. Comments inside code blocks are
preserved.
If a checked-in CLAUDE.md has already grown past the point of usefulness, /doctor produces trim
proposals rather than leaving you to guess what to cut.
Move it to a rule when it only applies to part of the repo
.claude/rules/ holds topic files — testing.md, api-design.md, security.md — discovered
recursively, so you can organise them into frontend/ and backend/ subdirectories.
A rule without paths frontmatter loads at launch with the same priority as .claude/CLAUDE.md. A
rule with paths loads only when Claude works with matching files:
---
paths:
- "src/api/**/*.ts"
---
# API development rules
- All API endpoints must include input validation
- Use the standard error response format
- Include OpenAPI documentation comments
That frontmatter is the whole point: your API conventions stop consuming context while Claude works on the front end. Path-scoped rules trigger when Claude reads a matching file, not on every tool use.
Brace expansion works — src/**/*.{ts,tsx} — but each brace group multiplies the pattern count, and a
rule's whole paths list shares a budget of 1,000 expanded patterns. A pattern that would exceed it is
used unexpanded, where its literal braces match nothing.
Personal rules in ~/.claude/rules/ apply to every project on your machine and load before project
rules, giving project rules higher priority.
Move it to a skill when it is a procedure or a reference
A skill is a directory with a SKILL.md: YAML frontmatter that tells Claude when to use it, and
markdown content Claude follows when it runs. The directory name becomes the command you type.
---
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullet points, then list any risks you notice.
The documented trigger is direct: create a skill when you keep pasting the same instructions, checklist, or multi-step procedure into chat, or when a section of CLAUDE.md has grown into a procedure rather than a fact.
That is the dividing line worth internalising. "We deploy with Helm" is a fact. The eleven steps of your deployment are a procedure.
Four properties make skills the right home for that content:
The body costs nothing until used. Only the description loads at session start. Long reference material — an API style guide, a schema, a runbook — is nearly free until the moment it is needed.
They can be invoked by name. /deploy is deterministic in a way "Claude should remember to
deploy correctly" is not.
The description is the trigger. Claude matches your task against skill descriptions to decide what
is relevant. Put the key use case first: description plus when_to_use is truncated at 1,536
characters in the skill listing.
They can be scoped and gated. paths frontmatter limits automatic activation to matching files.
disable-model-invocation: true hides a skill from Claude entirely until you invoke it — worth using
for anything with side effects, since it also drops the description's context cost to zero.
For authoring guidance, see how to create an AI agent skill; for what skills are in general, what are AI agent skills.
Imports are not a way around the size limit
CLAUDE.md supports @path/to/import syntax, with relative or absolute paths, recursing up to four
hops. Import parsing skips code spans and fenced blocks, so `@README` in backticks stays literal.
But imported files are expanded and loaded into context at launch. Splitting a 500-line CLAUDE.md into five imported files organises the source and changes the context cost not at all. Use imports for maintainability; use path-scoped rules or skills when the goal is to load less.
One import pattern is genuinely useful: @~/.claude/my-project-instructions.md shares personal
instructions across git worktrees, where a gitignored CLAUDE.local.md would only exist in the
worktree you created it in.
Note the trust boundary. An import in a project memory file whose path resolves outside the working directory is external, and Claude Code shows an approval dialog the first time it encounters one. Declining disables those imports permanently for that project. The dialog exists to protect you from files other people commit to a shared repository.
If your repo already has an AGENTS.md for other coding agents, Claude Code does not read it directly
— create a CLAUDE.md that imports it with @AGENTS.md, and add Claude-specific instructions below.
A worked refactor
A 400-line CLAUDE.md, split:
| Original section | Goes to | Why |
|---|---|---|
| Build and test commands | CLAUDE.md | Needed in every session |
| Directory layout | CLAUDE.md | Orientation, always relevant |
| "Use pnpm, not npm" | CLAUDE.md | Short, unconditional |
| 60 lines of React component conventions | .claude/rules/frontend.md with paths: ["src/components/**/*.tsx"] |
Only matters in the front end |
| 80 lines of SQL migration policy | .claude/rules/migrations.md with paths: ["db/migrations/**/*"] |
Only matters during a migration |
| The 11-step release checklist | .claude/skills/release/SKILL.md |
A procedure you trigger, not a fact |
| The full API style guide | .claude/skills/api-style/SKILL.md |
Reference material, needed sometimes |
| "Never commit to main" | A PreToolUse hook |
Must hold every time |
What used to load 400 lines into every request now loads perhaps 60, with the rest arriving when it is relevant.
After a refactor like this, delete the originals. Duplicated instructions in two places is the failure mode that follows, and the version that gets updated is rarely the version that gets loaded.
Troubleshooting
An instruction is being ignored. Check /context to confirm the file loaded. Then look for a
conflicting rule elsewhere in the hierarchy, and check whether the instruction is specific enough to
act on.
A skill never triggers. Its description probably does not match how you phrase the request. Put the
concrete use case first, and add trigger phrases via when_to_use. Or invoke it by name and stop
relying on inference.
A skill triggers too often. Narrow the description, add paths, or set
disable-model-invocation: true and invoke it yourself.
Someone else's CLAUDE.md is in your context. In a monorepo, claudeMdExcludes skips ancestor files
by path or glob. Put it in .claude/settings.local.json so the exclusion stays on your machine.
Next step: Browse Claude skills on LLM Mart and move one repeated procedure out of your persistent project instructions — the release checklist is usually the one that pays for itself first.
Sources
Comments (0)
Sign in to join the conversation.
No comments yet.