File change system-reminders inject full file contents repeatedly, context grows to 692K tokens

Status Fixed / completed
Reported on v2.1.81
Maintainer reply None cached
Activity 6 comments · opened Apr 4, 2026 · closed Apr 4, 2026

TL;DR: Editing lots of files in a long session causes token usage to explode — full file contents get re-injected into context on every turn and never get cleaned up.

Bug

When files are modified during a Claude Code session, <system-reminder> blocks containing full file contents are injected into the conversation context. These reminders persist and accumulate — they are not removed after being acknowledged. In a long session with many file edits, the context window fills with repeated file contents.

Verified data from session transcript

Extracted from the session's .jsonl transcript (usage field on each assistant message):

  • Total input tokens processed: 424,620,034 (424M)
  • Context size at session end: 692,190 tokens (near the 1M limit)
  • Cache read tokens: 402,378,746 (the same content re-read on every turn)
  • Output tokens: 248,911

The context grew steadily from ~50K tokens to 692K tokens over 923 messages. The growth is primarily from accumulated system-reminder file change notifications — 17 modified files injecting ~42K tokens of file contents per turn.

Files injected as system-reminders (measured)

| File type | Tokens per injection |
|-----------|---------------------|
| Large markdown file (500 lines) | 14,400 |
| Medium markdown file (320 lines) | 3,605 |
| TypeScript hook (270 lines) | 2,700 |
| TypeScript tool (250 lines) | 2,519 |
| TypeScript tool (240 lines) | 2,374 |
| TypeScript hook (200 lines) | 2,032 |
| Text evidence file (135 lines) | 1,969 |
| Shell script (190 lines) | 1,941 |
| Skill definition (180 lines) | 1,805 |
| Text evidence file (100 lines) | 1,668 |
| Text evidence file (150 lines) | 1,422 |
| TypeScript hook (120 lines) | 1,207 |
| + 5 smaller files | 3,851 |
| Total per turn | ~42,298 |

Reproduction

  1. Start a Claude Code session
  2. Use the Edit/Write tool to modify 10+ files (any mix of .ts, .md, .sh, .txt)
  3. Continue the conversation for 20+ turns
  4. Check the .jsonl transcript — observe cache_creation_input_tokens and cache_read_input_tokens growing with each message
  5. The system-reminder blocks are visible in the conversation as <system-reminder>Note: {file} was modified...</system-reminder> containing full file contents

Caveat

The token numbers above are verified from the session transcript. However, I cannot confirm that file change notifications are the sole cause of context growth. Other factors may contribute: conversation history, tool call results, compacted context from earlier messages, skills list injections, and other system-reminders. The 42K per-turn estimate is based on measuring the file sizes on disk — I cannot directly measure the injection frequency or confirm it happens on every turn vs selectively.

Suggestion

  • File change reminders should be injected once after the modification, then dropped from context
  • Or: include only the diff, not the full file
  • Or: provide a setting to disable file change notifications for files the model itself modified (it already knows what it wrote)

Environment

  • Claude Code 2.1.81
  • macOS
  • 923-message session, 17 modified files

View original on GitHub ↗

6 Comments

github-actions[bot] · 6 months ago

Found 3 possible duplicate issues:

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

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

gentry-tran · 6 months ago

Update: I've upgraded to 2.1.92 and ran a baseline test. On a fresh session with no file edits, the first assistant message consumed 20,209 cache_creation tokens — but that appears to be system prompt + tool definitions + skills/memory, not file change notifications.

The original report was about within-session accumulation after file edits. I haven't reproduced that on 2.1.92 yet.

Reproduction prompt for others: Paste this as your first message in a new session to check your baseline, then again after editing 10+ files over 20+ turns:

Run this command and show me the raw output:

LATEST=$(ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1)
python3 -c "
import json, sys
msgs = []
with open('$LATEST') as f:
    for line in f:
        d = json.loads(line)
        if d.get('type') == 'assistant' and 'usage' in d.get('message', {}):
            msgs.append(d['message']['usage'])

if not msgs:
    print('No assistant messages found')
else:
    first = msgs[0]
    last = msgs[-1]
    print(f'Messages: {len(msgs)}')
    print(f'First msg — input: {first.get(\"input_tokens\",0)}, cache_create: {first.get(\"cache_creation_input_tokens\",0)}, cache_read: {first.get(\"cache_read_input_tokens\",0)}, total: {first.get(\"input_tokens\",0)+first.get(\"cache_creation_input_tokens\",0)+first.get(\"cache_read_input_tokens\",0)}')
    print(f'Last msg  — input: {last.get(\"input_tokens\",0)}, cache_create: {last.get(\"cache_creation_input_tokens\",0)}, cache_read: {last.get(\"cache_read_input_tokens\",0)}, total: {last.get(\"input_tokens\",0)+last.get(\"cache_creation_input_tokens\",0)+last.get(\"cache_read_input_tokens\",0)}')
    print(f'Growth: {(last.get(\"input_tokens\",0)+last.get(\"cache_creation_input_tokens\",0)+last.get(\"cache_read_input_tokens\",0)) - (first.get(\"input_tokens\",0)+first.get(\"cache_creation_input_tokens\",0)+first.get(\"cache_read_input_tokens\",0))} tokens')
"

If "Growth" is significantly larger than expected from your conversation text alone, file change notifications may be accumulating. Please share your results and CC version.

gentry-tran · 6 months ago

Root cause analysis (traced from the v2.1.88 source leak):

The file change notification pipeline has no cleanup mechanism:

  1. L2Y runs every turn, scanning all files CC has ever read in the session. Any mtime change generates a diff.
  2. f47 wraps the diff in a <system-reminder> user message with isMeta: true — these are permanent and never pruned.
  3. The main loop concatenates all accumulated messages (including old notifications) into every subsequent API call.
  4. The only thing that clears them is autocompact, which doesn't fire until context already exceeds ~100K tokens.

Result: Each unique file modification creates a permanent message in the conversation. Linters, formatters, and auto-save each trigger new notifications. readFileState grows monotonically (every file ever read), expanding the scan surface.

Suggested fixes (ordered by impact):

  1. Make notifications ephemeral — inject for the current API call only, don't persist in conversation history
  2. Deduplicate per filename — keep only the latest notification per file
  3. Cap diff snippet size
  4. Add TTL-based pruning for isMeta file change messages

Still reproduces on 2.1.92 — the code path is unchanged.

gentry-tran · 6 months ago

Closing as duplicate of #4464 — same root cause. Posted the source-level analysis there.

gentry-tran · 6 months ago

Root cause analysis and discussion moved to #4464 — the canonical issue for this bug.

github-actions[bot] · 6 months 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.