[FEATURE] `paths:` frontmatter on rules and skills triggers on reads only, so neither loads when Claude creates a file
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Two things take paths: frontmatter: a rules file under .claude/rules/, and a
skill. The skills documentation says the field "uses the same format as
path-specific rules". Both are the mechanism for "load this only when Claude is
working with these files", and both are the reason I can keep language-specific
guidance out of every session.
Both trigger on reads only.
The rules page says it outright: "Path-scoped rules trigger when Claude reads
files matching the pattern, not on every tool use."
The skills page words the same field differently: a skill loads "only when
working with files matching the patterns". That reads like writing a file counts.
It does not, and the inconsistency between the two pages is part of what made
this take me a while to find.
The result is that the rule or the skill is absent in the one case where it
matters most: creating a new file.
Concretely. I keep a rules file scoped to JS and TS:
---
paths:
- "**/*.{js,jsx,mjs,cjs,ts,tsx}"
---
It says how comments should be written in those languages.
On Claude Code 2.1.267 I asked for a small TypeScript utility. Claude wrote
formatDuration.ts from scratch. It never read a .ts file, because none existed
yet, so the glob never matched, so the rules file never loaded, and Claude wrote
the comments in the form the rule forbids. I confirmed the absence with an
InstructionsLoaded hook, which the documentation recommends for exactly this:
the session log held the three CLAUDE.md files and no rules file.
A skill scoped with the same paths: value would have been just as absent from
that session. I cannot show you the same log line for it, because
InstructionsLoaded fires only for CLAUDE.md and .claude/rules/*.md and does not
report skill loads at all. So the rules half of this is measured and the skills
half is read off the documentation, which is also why I suspect the skills half
goes unnoticed.
Edits to files that already exist are covered, because Edit and Write against an
existing file require reading it first, so the rule or skill loads before the
edit lands. Creating a file is the entire gap, and it is precisely the moment a
"how to write this kind of file" instruction exists for.
There is a second failure mode on the same trigger. The documentation says a
path-scoped rule reloads after /compact only "as Claude reads files they apply
to", and that an instruction missing after compaction may be "a path-scoped rule
that hasn't matched a file since". So in a long session that is mostly writing,
a compaction drops it and nothing brings it back, because the trigger needs a
read that never comes.
Proposed Solution
Match paths: against the target path of a write, not only against reads, on
both surfaces that accept the field.
Keep the existing short form meaning "this is about these files", so nothing
written today has to change:
---
paths: ["**/*.ts"]
---
In a rules file, that would now load the rule on a read of a .ts file and on a
Write or Edit targeting one. In a SKILL.md, the same.
Add an optional on: to narrow it. A rule about how to write a file wants the
producing events and not the reading one:
---
paths: ["**/*.ts"]
on: [write, edit]
---
A skill that explains how to interpret a file type wants the opposite:
---
name: reading-migration-logs
paths: ["logs/migrations/**"]
on: [read]
---
Values: read, write, edit. Omitting on: means all three, so every rules file
and every skill written today keeps working and gains write loading.
Add a long form for when the patterns themselves differ per event. Database
migrations are the clearest real case, where one directory carries opposite
rules depending on whether you are adding a file or changing one:
---
triggers:
- on: edit
paths: ["migrations/**"]
- on: write
paths: ["migrations/**"]
---
Loading after the tool completes is acceptable, and it is what I would suggest
implementing. Loading before the write would mean interrupting a tool call the
model has already committed to, which is a far larger change. Loading
immediately after still fixes the rest of the session: the next file is covered,
and the model can revise the one it just wrote with the instruction in front of
it.
For the rules case, InstructionsLoaded would fire with load_reason
"path_glob_match" and trigger_file_path set to the written path, exactly as it
does for reads today.
Alternative Solutions
A PreToolUse hook that injects the text. This is what I built and run today: the
hook inspects the edit, and when it sees a violation of a rule whose file never
loaded, it injects that rule's text as additionalContext. It works for a rule
and it is not equivalent:
- additionalContext is capped at 10,000 characters. A rule fits. A skill body is
the wrong shape for it entirely, since a skill is a procedure meant to be
loaded and followed, not a paragraph appended to a tool result.
- Injected text does not appear under /context, so nothing can account for it.
- Nothing dedupes it, so it is re-sent on every fire rather than loading once.
- The hook has to reimplement glob matching and markdown extraction that Claude
Code already does internally.
- It only fires where I have written a deterministic check for the instruction.
Anything I cannot express as a function gets nothing.
Move the content into CLAUDE.md. That loads it in every session, including every
session that touches none of the relevant files, which is the cost paths:
exists to avoid. It is also not available to a skill: a skill has no
always-loaded equivalent, so for the skills half of this there is no fallback at
all.
Rely on a skill's description instead of paths:. A skill with no paths: field
loads when the model judges its description relevant to the prompt. That is a
real mechanism and it fails in exactly this scenario: the prompt was "write me a
small utility that formats a duration", which names no language, so nothing in it
points at a TypeScript skill. The file type only becomes knowable at the moment
the file is written, which is the moment paths: cannot see.
InstructionsLoaded. Cannot help. The documentation states it has no decision
control and cannot block or modify instruction loading. It is how I measured the
gap, not how I could close it.
Priority
Medium - Would be very helpful
Feature Category
Configuration and settings
Use Case Example
- I keep two things scoped to "**/*.{ts,tsx}": a rules file describing how
comments are written, and a skill covering the conventions of my TypeScript
stack. Both use paths: so they cost nothing in sessions that never touch
TypeScript, which is the whole reason I scoped them that way.
- I start a session and ask for a small TypeScript utility. No .ts file is
opened, because none exists yet.
- Claude writes formatDuration.ts. Neither the rules file nor the skill loaded,
so neither was in context, and the file comes out in the shape both of them
exist to prevent.
- With this change, the Write against formatDuration.ts matches the glob, both
load, and both are in context for the rest of the session. The next file is
right, and Claude can fix the one it just wrote.
Both are scoped to a file type precisely because they are about producing that
file type. Loading them only when reading is backwards for that entire class of
instruction.
Additional Context
Claude Code 2.1.267.
The two pages that describe the same field differently:
https://code.claude.com/docs/en/memory#path-specific-rules
"Path-scoped rules trigger when Claude reads files matching the pattern, not
on every tool use."
https://code.claude.com/docs/en/skills
"When set, Claude loads the skill automatically only when working with files
matching the patterns. Uses the same format as path-specific rules."
Resolving that wording either way would help on its own, independently of the
behaviour change.
How I measured it: https://code.claude.com/docs/en/hooks#instructionsloaded
An InstructionsLoaded hook appends one record per loaded file to a per-session
log. In the run above the log held the three CLAUDE.md files and no rules file,
which is what confirmed the rule was absent rather than present and ignored.
That hook covers CLAUDE.md and .claude/rules/*.md only, so it cannot report the
skill side of this at all.
Filed separately, because it is a different axis of the same field: paths:
has no exclude mechanism, so an extension glob matches node_modules and other
directories the instruction has no authority over. https://github.com/anthropics/claude-code/issues/93249
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗