Claude Code: CLAUDE.md vs rules vs skills vs subagents vs hooks
Claude Code gives you several places to put instructions, and at first they look like five ways of doing the same thing. A CLAUDE.md file holds instructions. So does a rule file, a skill and a subagent definition. Even a hook can push text into the conversation. The difference is not what you can write in them but when they load, how much context they cost, and whether anything forces them to happen.
The Claude Certified Architect, Foundations exam leans on exactly that difference. A typical question describes a team whose CLAUDE.md has grown huge, or whose "always run the formatter" line is followed only some of the time, or who want a read-only reviewer the whole team can use. Every option is a real Claude Code feature; the right one is the feature whose loading and enforcement model fits the requirement.
CLAUDE.md: always-on project context
CLAUDE.md is a Markdown file of instructions that Claude reads at the start of every session. It is the place for things Claude should always know: build and test commands, coding conventions, the project layout, and "never do X" rules.
Files can live at several levels, and they add up rather than replace each other:
- Managed policy, deployed by an organization to a fixed system path (for example
/etc/claude-code/CLAUDE.mdon Linux). It applies to every user on the machine and individual settings cannot exclude it. - User, at
~/.claude/CLAUDE.md, for your personal preferences across all projects. - Project, at
./CLAUDE.mdor./.claude/CLAUDE.md, committed so the team shares it. - Local, at
./CLAUDE.local.md, for personal notes about one project, kept out of git.
Files in the working directory and every directory above it load at launch. A CLAUDE.md in a subdirectory below it does not; it loads once Claude reads, writes or edits a file in that subdirectory. All the files that load are concatenated into context, ordered from the broadest scope to the most specific, so nothing deterministically "wins" a conflict. If two instructions contradict each other, Claude may pick one arbitrarily, which is why the docs tell you to remove conflicts rather than rely on ordering.
Two facts matter most. First, the whole file is in context on every request, so Anthropic recommends keeping each CLAUDE.md under about 200 lines; longer files cost more context and are followed less reliably. Second, CLAUDE.md is context, not enforced configuration. The docs say plainly that to block an action whatever Claude decides, you need a PreToolUse hook.
You can split a long file with @path/to/file imports, but imported files are expanded and loaded at launch alongside the file that imports them. Imports organize text; they do not save context.
Rules: CLAUDE.md that loads by file path
The .claude/rules/ directory holds extra Markdown files, one topic per file, such as testing.md or api-design.md. A rule file with no frontmatter loads at launch, just like .claude/CLAUDE.md. That only reorganizes your instructions.
The useful part is the paths field in YAML frontmatter. A rule with paths set to a glob such as **/*.test.ts loads only when Claude reads, writes or edits a file that matches. That is the answer whenever a question describes instructions that matter for one kind of file scattered across a large repository: testing conventions for test files, API rules for files under src/api/. A nested CLAUDE.md is tied to one directory; a path-scoped rule is tied to a pattern, so one file can cover every package.
Skills: knowledge and workflows loaded on demand
A skill is a folder with a SKILL.md file: YAML frontmatter with a name and a description, then a Markdown body of instructions. It can sit beside reference files and scripts. Project skills live in .claude/skills/<name>/ and can be committed; personal ones live in ~/.claude/skills/<name>/.
Skills load in stages. By default, only each skill's name and description sit in context; the full body loads when the skill is used, and bundled files are read only if the task needs them. A skill gets used in one of two ways: you type /<name>, or Claude matches your task against the description and loads it itself. That makes the description the field that decides whether a skill ever fires, and it is why a 40-page procedure belongs in a skill, not in CLAUDE.md. It costs almost nothing until someone needs it.
Two frontmatter settings control who can start a skill:
disable-model-invocation: truemeans only you can trigger it with/<name>. Its description is also kept out of context until then. Anthropic recommends this for skills with side effects, such as one that commits or deploys.user-invocable: falsedoes the opposite: Claude can still load it, but it is hidden from the/menu. It suits background knowledge nobody should run as a command.
Text typed after the command name replaces $ARGUMENTS in the body, so /fix-issue 123 can expand into a prompt about issue 123. Older .claude/commands/ files still work the same way.
A skill runs in your main conversation by default, so its content adds to your context window. Setting context: fork runs it in a separate subagent context instead.
Subagents: isolated workers with their own context
A subagent is a separate Claude instance with its own context window, its own system prompt, and its own tool access. You define one as a Markdown file in .claude/agents/ (project, share it through git) or ~/.claude/agents/ (personal). Only name and description are required; tools limits which tools it may use, and model can send it to a cheaper or stronger model. The Markdown body becomes its system prompt.
A subagent starts fresh. It does not see your conversation history or the files Claude has already read, so the task it is given has to carry what it needs. Only its summary comes back to the main conversation, however many files it read. Claude uses each subagent's description to decide when to delegate, just as it does with skills.
Reach for a subagent when the requirement is about isolation: a side task that would flood the main context with search results or logs, parallel work, or a specialist that must have different tools from the main session. A read-only security reviewer limited to Read, Grep and Glob is the classic case. A CLAUDE.md line asking Claude not to edit during reviews cannot limit tools, and deny rules in project settings would make every session read-only, not just the reviewer.
Hooks: things that must happen every time
A hook is code that Claude Code itself runs at a lifecycle event. Most hooks are shell commands; other types send an HTTP request, call an MCP tool, or ask a model to evaluate a condition. You configure them in a settings file: ~/.claude/settings.json for yourself, .claude/settings.json to share with the project, .claude/settings.local.json for personal project settings, or managed settings for an organization. Skills and subagents can also declare hooks in their frontmatter.
The events you will see most often:
PreToolUsefires before a tool call and can block it.PostToolUsefires after a tool call succeeds.SessionStartfires when a session begins or resumes; acompactmatcher fires it after compaction, which is a way to re-inject context.UserPromptSubmitfires when a prompt is submitted, before Claude processes it.
A matcher narrows a hook to particular tools, for example Edit|Write or Bash. The hook receives the event as JSON on stdin and answers through its exit code or stdout. Exit code 2 blocks the action, and the text written to stderr becomes the reason; on PreToolUse it is fed back to Claude so it can try something else. Exit 0 reports no objection. Most other non-zero codes are non-blocking errors: the action still goes ahead. For finer control, a hook can exit 0 and print a JSON decision instead.
This is the property that sets hooks apart. The docs describe hook triggers as guaranteed, while CLAUDE.md and skill instructions are followed by Claude's judgment and can vary. An instruction such as "never edit .env" is a request; a PreToolUse hook that blocks the edit is enforcement. Hooks also cost no context unless they return output.
How to decide
- Must it happen every single time, with no reliance on the model? Use a hook. Blocking something means PreToolUse with exit 2; reacting to a finished edit means PostToolUse.
- Does it need its own context window, its own tools, or to run alongside other work? Use a subagent.
- Is it a long procedure or reference material needed only for some tasks, or a workflow someone starts by name? Use a skill, with
disable-model-invocation: trueif it has side effects. - Does it apply only to certain files? Use a rule with
pathsfrontmatter. - Should Claude know it in every session, and is it short? Put it in CLAUDE.md.
Common exam traps
- Fixing an ignored CLAUDE.md line by writing it in capitals or repeating it. Emphasis does not make it guaranteed; a hook does.
- Using PostToolUse to stop a dangerous command. By then the command has already run.
- Exiting with code 1 from a hook meant to block. Only exit 2 (or a JSON decision) blocks.
- Moving instructions into an
@importor a rules file withoutpathsto save context. Both still load at launch. - Expecting the nearest CLAUDE.md to override the others. Files are combined, not ranked.
- Using a skill or slash command when the requirement is tool restrictions. A skill runs in the main conversation unless forked; a subagent's
toolsfield is what limits tools.
A worked example
A team's repository has a 450-line CLAUDE.md. It contains build commands, 120 lines of conventions for database migration files, a 30-step release checklist, and a line saying "run the linter after every edit," which Claude skips about once a session. They also want a dependency auditor that reads lock files across the repo without cluttering the main conversation.
Each part goes to a different home. The build commands and core conventions stay in CLAUDE.md, which shrinks well under 200 lines. The migration conventions become .claude/rules/migrations.md with a paths glob that matches the migration folder, so they load only when Claude touches those files. The release checklist becomes a release skill with disable-model-invocation: true, started with /release because it tags and publishes. The linter line becomes a PostToolUse hook on Edit|Write in .claude/settings.json, so it runs every time. The auditor becomes a subagent in .claude/agents/ with read-only tools, returning a short report.
Practice questions
- What loads when you launch from the repository root
- Testing rules that bloat a 600-line CLAUDE.md
- A 40-page procedure needed only for migrations
- A shared read-only security reviewer
- A formatter that CLAUDE.md can't make run
- Which exit code actually blocks a force push