Part of the AI Coding Harness Guide — environment design for coding agents in production.

Claude Code .claude/rules: Path-Scoped Rules That Load Only When Needed#

Published by

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.

PatternMatches
**/*.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#

VersionChange
v2.0.64.claude/rules/ added
v2.1.69Scoped rules load in claude -p; the InstructionsLoaded hook event added
v2.1.198Scoped rules load when the file is reached through a symlinked path
v2.1.211Nested rules respect --setting-sources without project
v2.1.288Write and Edit trigger scoped rules; before this, only Read did
v2.1.293A single-file cat, head, tail, sed -n or grep in Bash triggers them
v2.1.309A 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.

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 all lists loaded rules and imports under Memory files, with tokens. It runs headless: echo "/context all" | claude -p.
  • The InstructionsLoaded hook fires when CLAUDE.md or a rules file loads, so a hook can log which rules each session used.
  • claude --debug shows 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#

UseForLoads
Scoped ruleConventions for a file type spread across the treeOn a matching read, write or edit
Nested CLAUDE.mdEverything about one directoryOn any file in that directory; how nested CLAUDE.md loading works
SkillA procedure for a task, like a release or a migrationWhen invoked, or when Claude judges it relevant
Unscoped ruleShort rules for every taskAt 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.

Get the weekly digest

New Claude Code, Codex, Gemini CLI and OpenCode guides and release analyses, once a week, plus occasional notes.

One email a week plus occasional notes. Confirm by email; unsubscribe from any issue. Privacy