Claude Code .claude/rules: Path-Scoped Rules That Load Only When Needed#
Published by Alex Sorokoletov
A Markdown file in .claude/rules/ is an instruction file that Claude Code loads at launch, or, with a paths: list in its frontmatter, only when Claude reads, writes or edits a matching file. Rules shipped in v2.0.64, but they load reliably on every file tool only since v2.1.293. An @import inside a scoped rule still loads at launch.
Set up a rule#
Every .md file under .claude/rules/ is a rule, subfolders included. One topic per file, named for the topic. The official docs describe the layout:
your-project/
├── CLAUDE.md
└── .claude/
└── rules/
├── styling.md
├── testing.md
└── backend/
└── api.md
A rule without frontmatter loads at launch, with the same priority as .claude/CLAUDE.md. Add a paths: list and it waits for a matching file:
---
paths:
- "src/**/*.{ts,tsx}"
- "tailwind.config.ts"
---
# Styling
- Use the design tokens from src/theme.ts, never hex colors.
- Every interactive element gets a visible focus style.
paths is the only frontmatter field Claude Code reads; any other field is ignored. It takes a YAML list or a comma-separated string. Claude Code strips the frontmatter before the rule reaches the model.
Glob patterns#
Patterns are relative to the directory that holds the rule's .claude folder. Rules in ~/.claude/rules/ match from the session's primary working directory.
| Pattern | Matches |
|---|---|
**/*.ts | .ts files anywhere |
src/**/* | Everything under src/ |
/*.md | .md files in the base directory only |
src/**/*. | Both extensions; each brace group multiplies the patterns |
photos \[2024/** | A literal [; an unescaped one that is not a valid bracket expression matches nothing |
A rule's whole paths list shares a budget of 1,000 expanded patterns. A pattern past the budget stays unexpanded and its literal braces match no file.
What triggers a scoped rule, by version#
| Version | Change |
|---|---|
| v2.0.64 | .claude/rules/ added |
| v2.1.69 | Scoped rules load in claude -p; the InstructionsLoaded hook event added |
| v2.1.198 | Scoped rules load when the file is reached through a symlinked path |
| v2.1.211 | Nested rules respect --setting-sources without project |
| v2.1.288 | Write and Edit trigger scoped rules; before this, only Read did |
| v2.1.293 | A single-file cat, head, tail, sed -n or grep in Bash triggers them |
| v2.1.309 | A per-file size limit for rules that load on demand |
Source: the Claude Code changelog. On a build older than v2.1.288, a rule scoped to src/**/*.tsx never loaded when Claude created a new component with Write. On v2.1.309 and later, a scoped rule over 40,000 characters (200K context window) or 150,000 characters (1M window, current models) arrives truncated, with a note telling Claude to read the rest.
Gotcha: an @import in a scoped rule loads at launch#
Keeping the long version in docs/ and pointing a rule at it looks natural:
<!-- .claude/rules/styling.md: does NOT stay scoped -->
---
paths:
- "src/**/*.{ts,tsx}"
---
@../../docs/style-guide.md
On v2.1.319, /context all lists docs/style-guide.md under Memory files at launch, before Claude touches any file. The paths: scope holds the rule file, not what it imports. A scoped rule with inline text stayed unloaded in the same session, and deleting the import-only rule removed the doc from the launch list.
The fix that keeps the doc in place is a symlink. Put the frontmatter in the doc itself and link the rule to it:
# docs/style-guide.md now starts with the paths: frontmatter
ln -s ../../docs/style-guide.md .claude/rules/styling.md
In one repository, moving a styling doc and a testing doc this way cut launch memory from 14.8k to 11.2k tokens. A test file under src/ correctly pulled in both, and a session that read nothing had neither.
Symlinks and shared rules#
Rules can be symlinks to files or whole folders, which is how a team shares one set across repositories:
ln -s ~/shared-claude-rules .claude/rules/shared
ln -s ~/company-standards/security.md .claude/rules/security.md
A target outside the working directory counts as an external import. It loads only after the one-time approval dialog, and then only rules without paths: load: a scoped rule linked from outside the project never loads. Links into the same repository, like the docs/ symlink above, need no approval and keep their scope. Links to network paths (\\server\share, /net, /Network) are not followed.
User-level rules#
~/.claude/rules/ holds rules for every project on your machine, loaded before project rules. Neither overrides the other; on a conflict Claude may follow either, so keep them consistent. User-level rules load without the external-import dialog.
Check that a rule loaded#
/context alllists loaded rules and imports under Memory files, with tokens. It runs headless:echo "/context all" | claude -p.- The
InstructionsLoadedhook fires when CLAUDE.md or a rules file loads, so a hook can log which rules each session used. claude --debugshows a frontmatter parse error. Invalid YAML makes Claude Code drop the frontmatter and load the rule at launch, unscoped.- A behavior test: ask for a fact only the rule contains, with file tools off, before and after reading a matching file.
Rules, nested CLAUDE.md or skills#
| Use | For | Loads |
|---|---|---|
| Scoped rule | Conventions for a file type spread across the tree | On a matching read, write or edit |
| Nested CLAUDE.md | Everything about one directory | On any file in that directory; how nested CLAUDE.md loading works |
| Skill | A procedure for a task, like a release or a migration | When invoked, or when Claude judges it relevant |
| Unscoped rule | Short rules for every task | At launch |
FAQ#
What is the .claude/rules folder in Claude Code? A folder of Markdown instruction files. A file without frontmatter loads at launch; a file with a paths: list loads when Claude reads, writes or edits a matching file.
Why isn't my path-scoped rule loading? Check the version (Write and Edit trigger rules from v2.1.288, Bash cat from v2.1.293), the glob base (the folder that holds .claude), and whether the rule is a symlink to a file outside the project, which never loads with paths:.
Why does my scoped rule load at launch? Either its YAML frontmatter does not parse, which drops the scope, or it reaches its content through an @import, which loads at launch. Run claude --debug and /context all.
Can a rule import another file? It can, but the imported file loads at launch, whatever the rule's paths:. Use a symlink with the frontmatter in the target to keep the scope.
What frontmatter fields does a rule support? Only paths. Other fields are ignored without an error.