.claude/rules/ and paths: — instructions that load when they are relevant
By the end you can
- Split a growing CLAUDE.md into topic files under .claude/rules/
- Predict when a rule loads — at launch, or on a matching file read
- Write paths globs that match what you intended, including the edge cases
- Choose between a per-directory CLAUDE.md and a central path-scoped rule
- Keep a constraint that must always hold out from behind a path-scoped trigger
- Keep other teams' instruction files out of your window with claudeMdExcludes
Your CLAUDE.md has crossed 200 lines and you can see exactly why: forty of
those lines are database conventions that only matter under src/db/, and
thirty are React rules nobody working on the API will ever need. Lesson 6.2 told
you to route an instruction that "only matters for one part of the codebase"
somewhere else. This is that somewhere else, and the one thing it asks you to get
right is when each file arrives.
One file per topic
.claude/rules/ is a directory of markdown files, each covering one subject.
Nesting works, and you register nothing:
.claude/
├── CLAUDE.md # Main project instructions
└── rules/
├── testing.md # Testing conventions
└── frontend/
└── react.md # Discovered recursively~/.claude/rules/ is the personal equivalent, applying to every project on your
machine. The docs give the ordering — user-level rules are loaded before project
rules, "giving project rules higher priority" — but nowhere say that a project
rule overrides a conflicting personal one. What they do say is the part to design
around: if two rules contradict each other, Claude may pick one arbitrarily
(How Claude remembers your project). A
personal rule disagreeing with the project rule is a defect in both, not a losing
bid.
An unscoped rule is CLAUDE.md under a different filename
This is the sentence people skip:
Rules without
pathsfrontmatter are loaded at launch with the same priority as.claude/CLAUDE.md. — How Claude remembers your project
Splitting a 300-line CLAUDE.md into six tidy rule files buys exactly what @
imports bought in Lesson 6.1: organization, and not one token of context back.
Six files with owners beat one file with none, so the maintainability is real —
but everything still loads at launch, so if you did this to shrink startup
context you have not yet done anything. The savings are in the next section.
paths: and what actually fires it
Add a paths list to a rule's frontmatter and it stops loading unconditionally:
---
paths:
- "src/api/**/*.ts"
---
- All API endpoints must include input validation
- Use the standard error response formatRules without a
pathsfield are loaded unconditionally and apply to all files. Path-scoped rules trigger when Claude reads files matching the pattern, not on every tool use. — How Claude remembers your project
Read the second sentence twice. The trigger is Claude reading a matching
file — not you mentioning the directory, not the task being obviously about
the API. Until a matching read happens the rule is not in the window and cannot
influence anything. The documented examples cover the four shapes you will reach
for: **/*.ts for every TypeScript file in any directory, src/**/* for
everything under src/, *.md for markdown in the project root,
src/components/*.tsx for one directory's React components.
Globs that do what you meant
Two edge cases account for most rules that silently do nothing.
Brace expansion multiplies. src/*.{ts,tsx} expands to two patterns and
{a,b}/{c,d}/*.{ts,tsx} to eight. A rule's whole paths list shares one budget
of 1,000 expanded patterns and 4 MiB, and patterns without braces do not count
against it. Blow the budget and the failure is quiet: any pattern that would
exceed it is used unexpanded, and its literal braces then match no files.
A square bracket is glob syntax, not a character. Glob reads [ as the start
of a bracket expression such as [abc]. A pattern with a [ that cannot be read
as one — the docs' example is photos [2024/** — is invalid: it matches nothing,
while the rule's other patterns keep working. Escape it as photos \[2024/**
when you meant the literal character
(How Claude remembers your project).
Both failures are per-pattern and silent, so a rule can be half-live without ever
looking broken, and a rule that matches nothing looks identical to a rule Claude
ignored. The check is therefore the session, not the glob: read a file in the
target directory, then run /context, which the
docs name as the way "to check which files actually loaded into the current
session" (How Claude remembers your project).
Per-directory CLAUDE.md, or a central rule?
Two ways to target part of the tree, and the docs put the difference in mechanics rather than in subject matter:
Per-directory
CLAUDE.mdfiles and path-scoped rules under.claude/rules/both let you target instructions to part of the tree. They differ in where the file lives and when it loads. — Monorepos and large repos
Per-directory CLAUDE.md | Path-scoped rule | |
|---|---|---|
| File location | Inside the directory, alongside its code | Central .claude/ at the repo root |
| Loads when | At launch when started from that directory, or on demand when Claude reads a file there | When Claude works with a file matching the rule's paths: glob |
| Use when | Directory owners maintain their own conventions; instructions are versioned with the code | You want all conventions in one place, or the same rule applies to many scattered paths |
The deciding row is usually the first: does the convention belong to the team
that owns that directory, or to the repository? There is a third factor, and you
set it from the shell rather than a file. Where you launch claude decides which
files it can read without a further permission grant, which CLAUDE.md files
load at startup, and which project settings apply. Start in packages/api/ and
that package's file plus every ancestor's loads at launch, with nothing from
packages/web/ in context; start at the repository root and only the root file
loads at launch, each subdirectory's waiting for a read there
(Monorepos and large repos).
Neither answer is a skill. Rules load every session or when matching files are opened; task-specific instructions that need not sit in context at all belong in a skill, which loads only when you invoke it or when Claude judges it relevant to your prompt. Writing one is Level 7 — here it is only the destination.
Excluding what you never work in
The mirror image of scoping your own rules is refusing other people's.
claudeMdExcludes skips ancestor and subdirectory files by path or glob:
{
"claudeMdExcludes": [
"**/monorepo/CLAUDE.md",
"/home/user/monorepo/other-team/.claude/rules/**"
]
}Patterns match against absolute file paths, and the key is configurable at any settings layer — user, project, local, or managed policy — with the arrays merging across layers rather than overriding. That merge matters: a project-level exclusion adds to your user-level list, it does not replace it.
Keep personal exclusions in .claude/settings.local.json, and mind where you put
that file. A root .claude/settings.local.json loads in every session inside the
repository regardless of the directory you started in, so launching in
packages/api/ still carries your root-level exclusions with you — except where
Claude Code does not use the repository root, such as on Windows.
Before v2.1.211 it loaded only from the starting directory
(Monorepos and large repos).
Two limits. Exclusions reach only user, project and local memory files: managed
policy CLAUDE.md cannot be excluded, so organization-wide instructions apply
regardless of individual settings
(Settings reference).
And the one more likely to catch you:
The exclusion list is static, not a per-task switch. To focus on one package today and another tomorrow, start Claude from that package's directory instead of editing exclusions. — Monorepos and large repos
Exclusions are for directories you never work in — vendored subtrees, legacy
code, another team's packages. Today's focus is a cd.
The trap: a permanent rule behind a temporary trigger
paths: is such an obvious answer to a long CLAUDE.md that it gets applied to
rules which must always hold. Take "never edit a migration after it has merged",
scoped to db/migrations/**. It is absent until Claude reads a file there — and
then Lesson 3.2's compaction table takes it away again:
Path-scoped rules and nested CLAUDE.md files load into message history when their trigger file is read, so compaction summarizes them away with everything else. — Context window
Right after compaction Claude Code re-reads up to five of the recently modified files and reloads the rules applying to those, so a scoped rule may return. Whether yours does depends on which five those were.
So the routing question is not "does this apply to one directory?" It is "is
this a convention for that code, or a constraint on my session?" Conventions
scope happily; constraints do not. And a constraint that must hold regardless of
what Claude decides was never an instruction file's job at all: a rule is
guidance Claude reads, exactly as CLAUDE.md is, and a guarantee comes from a
hook or a permission rule (Explore the .claude
directory). Lesson 6.2 draws
that line.
Checkpoint
-
You move 120 lines out of
CLAUDE.mdinto four files under.claude/rules/and add no frontmatter. What has changed about your startup context, and what has not?Show answerAnswer
Nothing has changed about your startup context. Rules without
pathsfrontmatter are loaded at launch with the same priority as.claude/CLAUDE.md, so the same 120 lines arrive in the same window — this is exactly what@imports bought in Lesson 6.1: organization, and not one token back.What did change is maintainability, and that part is real: four files with owners beat one file with none. What has not changed is the length problem the move was probably meant to solve. The savings only start when you add a
paths:list to a rule and it stops loading unconditionally. -
A rule scoped to
src/api/**/*.tssays every endpoint needs input validation. Claude adds an unvalidated endpoint 90 minutes into a session that has compacted twice. Name two distinct reasons the rule might not have been in the window, and the one-line change that removes both.Show answerAnswer
First, it may never have loaded. A path-scoped rule triggers when Claude reads a file matching the pattern, not on every tool use — not because you mentioned the API and not because the task is obviously about it. Second, if it did load it was summarised away: path-scoped rules load into message history when their trigger file is read, so compaction takes them with everything else. Claude Code re-reads up to five recently modified files after a compaction and reloads the rules applying to those, so the rule may have returned — which depends on which five those were, and is not something you can rely on twice over.
The one-line change is to delete the
paths:frontmatter, which makes the rule load unconditionally at launch; moving it into the project-rootCLAUDE.mddoes the same job. That is the lesson's routing question: this is a constraint on your session rather than a convention for that code, and constraints do not scope happily. If it must hold regardless of what Claude decides, an instruction file was never the right mechanism at all — that is a hook or a permission rule. -
A convention applies to
web/,mobile/andadmin/, each with its own owners and its ownCLAUDE.md. Per-directory file or central path-scoped rule, and what makes it that one?Show answerAnswer
A central path-scoped rule. The deciding question is whether the convention belongs to the team that owns the directory or to the repository, and this one spans all three directories, so it is the repository's. That is also the documented use case for the central form in as many words: you want all conventions in one place, or the same rule applies to many scattered paths — here, one rule with a
pathslist coveringweb/,mobile/andadmin/.The pull toward the per-directory answer is the "own owners, own
CLAUDE.md" detail, and it is the trap: three copies of one convention is three files to keep in step, and the moment they drift you have rules that contradict each other, where Claude may pick one arbitrarily. Per-directory files earn their place when each directory's conventions genuinely differ and its owners maintain them alongside the code. -
You write a
pathsentry oflogs [2026/**and the rule never fires. What happened, and did it break the rule's other patterns?Show answerAnswer
The
[is glob syntax, not a character. Glob reads it as the start of a bracket expression such as[abc], andlogs [2026/**cannot be read as one, so the pattern is invalid and matches nothing. Writelogs \[2026/**when you meant the literal bracket.The rule's other patterns keep working — the failure is per-pattern and silent, which is why a rule can be half-live without ever looking broken, and why a rule that matches nothing looks identical to a rule Claude ignored. Check the session rather than the glob: read a file in the target directory, then run
/context. One version note, since the symptom depends on it: matching nothing is the behaviour from v2.1.207, and before that a single bad pattern made the Read tool fail for every file the rule was evaluated against.