Rules re-injected as system-reminders on every tool call, consuming ~46% of context window

Status Closed — not planned
Maintainer reply None cached
Activity 9 comments · opened Mar 8, 2026 · closed May 25, 2026

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 Read tool 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

  1. Create a project with 10-15 rule files in .claude/rules/ scoped to common patterns (e.g. **/*.py)
  2. Start a conversation and perform several file reads/edits on matching files
  3. Observe <system-reminder> blocks containing full rule text appearing in tool results
  4. 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

View original on GitHub ↗

9 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/21214
  2. https://github.com/anthropics/claude-code/issues/27599
  3. https://github.com/anthropics/claude-code/issues/16021

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

Sagargupta16 · 5 months ago

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/ minimal
Consolidate 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 /compact proactively
Run /compact before 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

lilpacy · 5 months ago

Additional data point from OTEL telemetry capture:

I captured api_request events with token counts alongside InstructionsLoaded hook events for a single session. The nested_traversal mechanism re-injects the same subdirectory CLAUDE.md every time Claude reads a file in that directory.

Session: f3736323-1a26-460f-9dc9-845ee0c4c9b6

InstructionsLoaded 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_request OTEL events:
Time      total_context_tokens
03:52:10  26,973   ← before first load of apps/web/CLAUDE.md
03:52:17  45,593   ← after first load (+18,620 tokens)

The same ~18k-token CLAUDE.md was injected 3 times during this session.

Environment: Claude Code 2.1.72, macOS Darwin 25.2.0

poodle64 · 5 months ago

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:

  • This issue (#32057): Fix paths: so matching rules load once, not re-injected per tool call
  • #34209: Allow projects to control which ancestor rules they inherit

Both are needed for efficient rule management in monorepo/multi-project setups.

poodle64 · 5 months ago

Update on v2.1.74 testing

Tested using the InstructionsLoaded hook (Claude Code's own infrastructure, not model self-reporting) across three separate sessions from a Python project with a canary rule using paths: ['**/*.py'] frontmatter.

What is working in v2.1.74:

  • paths: frontmatter scoping works correctly — rules with paths: ['**/*.py'] only trigger InstructionsLoaded when a matching file is read
  • The hook fires once per rule per session (correct deduplication)

The working YAML format:

---
paths:
  - '**/*.py'
---

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 InstructionsLoaded hook 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.

poodle64 · 4 months ago

Update on v2.1.97 testing

Re-ran the InstructionsLoaded hook test on Claude Code 2.1.97 (macOS). The split status from v2.1.74 is unchanged:

Still working: paths: frontmatter scoping. Rules with paths: ['**/*.py'] only fire path_glob_match when a matching file is read. Deduplication within a session is correct — reading a second .py file 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:

Loaded ../../rules-library/core/20-code-quality.md
Loaded ../../rules-library/core/20-security-standards.md
Loaded ../../rules-library/core/10-workflow-locale.md
Loaded ../../.claude/rules/BUG2-CANARY.md
Loaded .claude/rules/73-verification.md
Loaded ../../rules-library/python/20-python-basics.md
Loaded .claude/rules/00-project-foundations.md
Loaded .claude/rules/70-error-handling.md
Loaded .claude/rules/50-scraper-provider-patterns.md
Loaded .claude/rules/20-cli-patterns.md
Loaded .claude/rules/20-development-environment.md
Loaded ../../rules-library/python/20-python-config.md

The InstructionsLoaded hook 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.md path. Example from the log:

00:37:47  [Project]  (session_start)  /Users/me/projects/.claude/CLAUDE.md
00:37:47  [Project]  (session_start)  .claude/CLAUDE.md
00:37:47  [Project]  (session_start)  /Users/me/projects/rules-library/core/75-task-tracking.md  (appears twice)
00:37:47  [Project]  (session_start)  /Users/me/projects/rules-library/core/devenv/20-nix-package-policy.md  (appears twice)

Happens when a sub-project's .claude/rules/core is 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.

github-actions[bot] · 3 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

anka-213 · 3 months ago

If I resume a claude session after closing it, all the paths: triggered rules get re-injected again. Have you also experienced this?

github-actions[bot] · 1 month ago

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.