Rules re-injected as system-reminders on every tool call, consuming ~46% of context window
Problem
Claude Code re-injects rule files (from .claude/rules/) as <system-reminder> blocks attached to tool call results throughout the conversation. This means the same rules are loaded into context repeatedly — not just once at conversation start.
In a real session with ~30 tool calls, this consumed approximately 93K tokens (46% of the 200K context window) just from rule re-injection, causing the context to fill up before meaningful work was completed.
Observed behaviour
- Rules with
paths:YAML frontmatter are correctly filtered (only matching rules appear) - However, matching rules are re-injected as
<system-reminder>blocks on tool results throughout the conversation - A single
Readtool call resulted in 11 rule files (~6,200 tokens) being injected as system reminders - Over a 30-tool-call session, the same rules are injected repeatedly, totalling ~93K tokens
Expected behaviour
Rules should be loaded into context once (or at most when the set of relevant files changes), not re-injected on every tool call.
Reproduction
- Create a project with 10-15 rule files in
.claude/rules/scoped to common patterns (e.g.**/*.py) - Start a conversation and perform several file reads/edits on matching files
- Observe
<system-reminder>blocks containing full rule text appearing in tool results - Note context window fills rapidly despite rules being modest in size individually
Impact
| Component | Tokens | % of 200K |
|-----------|--------|-----------|
| Initial config load | ~43K | 21% |
| Rule re-injections (~30 tool calls) | ~93K | 46% |
| Actual conversation content | ~50K | 25% |
| Total | ~186K | 93% |
The initial load (21%) is reasonable. The re-injection (46%) is the problem — nearly half the context window is consumed by repeatedly injecting rules the model has already seen.
Suggestion
- Inject rules once at conversation start (or on first relevant file access)
- Only re-inject if the set of relevant rules changes (e.g. user starts working on a different file type)
- Consider a hash/fingerprint approach to avoid re-injecting unchanged rules
Environment
- Claude Code VSCode extension
- ~45 master rule files (shared via symlinks) + 8 project-specific rules
- Rules use
paths:frontmatter for scoping - Model: Claude Opus
9 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
This is a real pain point — I've measured similar overhead in my own sessions. Here are some practical workarounds until this gets fixed:
1. Keep
.claude/rules/minimalConsolidate multiple rule files into one lean file. Each separate file gets re-injected independently, so 5 files × 30 tool calls = 150 re-injections vs 1 file × 30 = 30.
2. Move stable rules into CLAUDE.md instead
CLAUDE.md content benefits from prompt caching (90% discount on cache hits — $0.50/MTok vs $5.00/MTok on Opus 4.6). Rules injected as system-reminders may break the cache prefix on each tool call.
3. Use
/compactproactivelyRun
/compactbefore hitting the wall. The context compression can reclaim significant space.4. Budget your rule lines
The math: 100 lines of rules ≈ ~500 tokens. At 30 re-injections = 15K tokens wasted. At 60 re-injections = 30K tokens. Keep rules under 50 lines total — every line pays a multiplicative cost.
I wrote a detailed guide on context optimization strategies including this exact issue: Context Optimization Guide
Additional data point from OTEL telemetry capture:
I captured
api_requestevents with token counts alongsideInstructionsLoadedhook events for a single session. Thenested_traversalmechanism re-injects the same subdirectoryCLAUDE.mdevery time Claude reads a file in that directory.Session:
f3736323-1a26-460f-9dc9-845ee0c4c9b6InstructionsLoaded events (same file loaded 3 times):
| Time | load_reason | file_path |
|------|-------------|-----------|
| 03:52:12 | nested_traversal |
.../apps/web/CLAUDE.md|| 04:03:57 | nested_traversal |
.../apps/web/CLAUDE.md|| 04:04:14 | nested_traversal |
.../apps/web/CLAUDE.md|Token counts from
api_requestOTEL events:The same ~18k-token CLAUDE.md was injected 3 times during this session.
Environment: Claude Code 2.1.72, macOS Darwin 25.2.0
Related: #34209 covers a complementary problem — even if
paths:re-injection is fixed, there's no mechanism for sub-projects to exclude inherited rules from parent directories. Claude Code's parent directory traversal loads all ancestor.claude/rules/unconditionally, so a Swift-only project inside a multi-language governance tree still loads Python/Svelte/Rust rules at session start.The two issues work together:
paths:so matching rules load once, not re-injected per tool callBoth are needed for efficient rule management in monorepo/multi-project setups.
Update on v2.1.74 testing
Tested using the
InstructionsLoadedhook (Claude Code's own infrastructure, not model self-reporting) across three separate sessions from a Python project with a canary rule usingpaths: ['**/*.py']frontmatter.What is working in v2.1.74:
paths:frontmatter scoping works correctly — rules withpaths: ['**/*.py']only triggerInstructionsLoadedwhen a matching file is readThe working YAML format:
What is still broken:
The core problem in this issue — rules being re-injected as
<system-reminder>blocks on every tool call — is still happening. I observed it directly: a single Grep tool call resulted in 9 full rule files being re-injected as<system-reminder>blocks attached to the tool result, despite those rules having already been loaded earlier in the session.The
InstructionsLoadedhook and the<system-reminder>re-injection are two separate mechanisms. The hook correctly fires once; the re-injection happens on every tool result regardless.Leaving open for Anthropic to confirm and close.
Update on v2.1.97 testing
Re-ran the
InstructionsLoadedhook test on Claude Code 2.1.97 (macOS). The split status from v2.1.74 is unchanged:Still working:
paths:frontmatter scoping. Rules withpaths: ['**/*.py']only firepath_glob_matchwhen a matching file is read. Deduplication within a session is correct — reading a second.pyfile does not re-trigger already-loaded rules.Still broken:
<system-reminder>re-injection on tool calls.Fresh session in a Python sub-project, three file reads (
cache.py,config.py,pyproject.toml). Claude Code surfaced 12 rule files as<system-reminder>blocks attached to tool results in a single load banner:The
InstructionsLoadedhook fired once per rule (correct); the system-reminder injection is a separate mechanism and still wasteful.Additional data point — duplicate loading via symlinks. In the same session the hook log shows the same CLAUDE.md and core rule files being registered twice at
session_start, once under their resolved absolute path and once under the sub-project's relative.claude/CLAUDE.mdpath. Example from the log:Happens when a sub-project's
.claude/rules/coreis a symlink to the same physical directory the parent traversal also finds. Claude Code is not deduplicating across the resolved path, which compounds the re-injection cost.Environment: Claude Code 2.1.97, macOS Darwin 25.1.0.
Leaving open.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
If I resume a claude session after closing it, all the
paths:triggered rules get re-injected again. Have you also experienced this?This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.