Prose, config, and code
The single most useful distinction to hold in your head is what kind of thing you are writing. Every mechanism in Claude Code falls into one of three families, and the family determines what happens when two pieces of configuration disagree.
| Kind | Mechanism | What “conflict” means |
|---|---|---|
| Prose | CLAUDE.md, rules, skills, output styles | Text concatenated into the prompt. Conflicts are resolved by the model’s judgement, i.e. non-deterministically |
| Config | settings.json, agent frontmatter, permissions | Structured values. Conflicts resolved by a defined precedence order, deterministic |
| Code | Hooks | Shell commands that run and can block. Not a conflict at all, it either fires or it doesn’t |
The one-line takeaway: prose is a request, config is a rule, hooks are enforcement. If something absolutely must happen every time, it belongs in a hook, not in CLAUDE.md.
That framing resolves most design questions before you have to think about them. “Never commit directly to main” is not a preference, it is a rule that must hold, so it is a hook. “Prefer table-driven tests” is a preference the model should weigh against context, so it is prose. “Use this JDK” is a value, so it is a setting.
The layer stack
graph TD
subgraph ENFORCE["Enforcement — deterministic, blocking"]
H["Hooks<br/>PreToolUse / PostToolUse / SessionStart<br/>shell commands that can block a tool call"]
end
subgraph CONFIG["Configuration — deterministic precedence"]
S["settings.json<br/>env, permissions, model, outputStyle"]
P["Permissions<br/>allow / ask / deny"]
end
subgraph ISOLATE["Isolation — separate context windows"]
A["Subagents<br/>.claude/agents/*.md<br/>own prompt, tools, model"]
end
subgraph PROSE["Prose — concatenated, model-arbitrated"]
M["CLAUDE.md family<br/>always in context"]
R["Rules<br/>.claude/rules/*.md<br/>path-scoped"]
K["Skills<br/>description always,<br/>body on demand"]
O["Output styles<br/>replaces system prompt"]
end
subgraph PACKAGE["Packaging"]
PL["Plugins<br/>bundle any of the above<br/>for distribution"]
end
PL -.->|"can contain"| M
PL -.->|"can contain"| K
PL -.->|"can contain"| A
PL -.->|"can contain"| H
PL -.->|"can contain"| S
Plugins sit outside the stack rather than on top of it. A plugin is a distribution format, not a behaviour: it bundles memory, skills, agents, hooks and settings so a second repository can adopt the whole set in one command.
Cost profile
Not every layer costs the same in context tokens. This matters a lot as a project grows, and it is the reason “put everything in CLAUDE.md” stops working.
| Mechanism | Context cost when unused | Context cost when used |
|---|---|---|
CLAUDE.md |
Full text, every turn | Same |
Rules with paths: |
~Zero until a matching file is touched | Full text |
Rules without paths: |
Full text, every turn | Same |
| Skill | Only the description line |
Description + full body |
Skill with disable-model-invocation: true |
Zero, description not loaded either | Full body |
| Subagent | Only its description |
Runs in a separate context window; only a summary returns |
| Output style | Replaces part of the system prompt | Same |
| Hook | Zero | Zero, runs outside the model |
CLAUDE.md is the most expensive place to put anything, because it is paid for on every single turn whether relevant or not. A 600-line CLAUDE.md in a monorepo is charged in full during a session that only touches one CSS file.
What that costs in practice
Take a monorepo with four languages and a 900-line CLAUDE.md holding every style guide. Roughly:
| Arrangement | Tokens on a TypeScript-only session |
|---|---|
Everything in CLAUDE.md |
~12,000, of which ~9,000 is Java, Go and Python convention |
Split into .claude/rules/*.md with paths: |
~3,000, the rest never loads |
| Reference material moved into skills | ~3,000 plus ~40 tokens of skill descriptions |
The second and third arrangements are also more reliable, not just cheaper. Adherence measurably degrades on long instruction files, so cutting the file down improves how closely the remaining instructions are followed.
Two properties that surprise people
Nested memory and path-scoped rules load lazily. A CLAUDE.md in a
subdirectory you never open costs nothing; it enters context the moment Claude
reads a file in that directory. A rule with paths: ["**/*.go"] is simply absent
from a TypeScript session. This is the whole monorepo pattern.
Only some layers actually override each other. Memory files and rules are additive: every discovered file is concatenated and nothing wins. Settings, skills, and agents genuinely override. Hooks are additive again, and all of them run. Conflicting rules covers each case, including the two subsystems that resolve in opposite directions.
Where to go next
If you are configuring a repository for the first time, read what loads at session start, then memory files and rules. If you are debugging something that already exists and is misbehaving, skip straight to conflicting rules and the checklist at the end of it.