# The Bigger Your CLAUDE.md Gets, the Slower Claude Code Runs — Measuring Context Injection Bytes Weekly to Catch Degradation Early

> Source: <https://dev.to/bokuwalily/the-bigger-your-claudemd-gets-the-slower-claude-code-runs-measuring-context-injection-bytes-j84>
> Published: 2026-07-24 11:00:06+00:00

This is the next installment in my "Claude Code environment" series. In the previous post on the [self-audit frustration loop](https://zenn.dev/bokuwalily/articles/selfaudit-frustration-loop), I wrote about a "never-ending improvement loop." This time I want to dig into what sits at the root of it: **detecting and self-correcting context injection bloat**.

At the start of every session, Claude Code unconditionally injects the full contents of `rules/*.md`

, `~/.claude/CLAUDE.md`

, `~/CLAUDE.md`

, and `MEMORY.md`

. Every time you add a rule, this byte count silently grows. In an audit on 2026-07-11, I found that **total injection had exceeded 40KB, and 99 agents I thought I had archived were still being loaded** — the main cause of my performance degradation. Since then, I've kept a permanent loop running that measures this automatically every week and attempts self-correction when thresholds are exceeded.

Adding rules to CLAUDE.md is a good thing. But "adding" only moves in one direction. After half a year, forgotten sections get duplicated, an archive you thought was compressed turns out to still be live, and files you moved to `.agents/`

keep getting loaded anyway.

The core issue is that **degradation is invisible unless you measure it**. Claude's gut feeling of "this seems sluggish" may be correct, but nobody routinely checks how many KB are being injected. I needed a mechanism that produces the numbers weekly and automatically attempts a fix when a human-defined threshold is exceeded.

The `collect()`

function in `~/.claude/scripts/cc-self-audit.sh`

assembles the metrics. There are two axes: static and dynamic measurement.

```
inject_bytes=$(( \
  $(find "$HOME/.claude/rules" -name '*.md' -print0 2>/dev/null | xargs -0 cat 2>/dev/null | wc -c) + \
  $(cat "$HOME/.claude/CLAUDE.md" 2>/dev/null | wc -c) + \
  $(cat "$HOME/CLAUDE.md" 2>/dev/null | wc -c) + \
  $(cat "$HOME/.claude/projects/-Users-matsubara/memory/MEMORY.md" 2>/dev/null | wc -c) ))
agents_loaded=$(find "$HOME/.claude/agents" -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
```

`find ... -name '*.md'`

recurses into dot directories too. Even if you think "I moved them to `agents-archive/`

, so it's fine," if that directory lives under `~/.claude/agents/`

as a dot directory, the files **keep getting loaded** — that's the trap (this is exactly how 99 agents survived on 2026-07-11).

Three metrics are aggregated from the transcripts (`.jsonl`

) since the last run.

```
STOP_MARKER = 'Stop hook feedback:\\n[~/.claude/hooks/self_audit_stop.sh]: '
FRUST_RE = re.compile(r'何回も言|いい加減にし|嘘つ|舐めんな|なんで治らん|最悪やろ|頭悪い')
```

stopspam counts only "lines that the Stop hook actually injected into the prompt." A naive grep also picks up the same string when it happens to appear inside a tool_result, inflating the number 6–10x over reality (I fixed exactly this on 2026-07-12, correcting 40 → 6). For frustration, only text blocks of `type=user`

that are not `isMeta`

— i.e., the human's own raw utterances — are counted.

```
TH_INJECT_BYTES="${SELF_AUDIT_TH_INJECT:-40000}"   # rules+CLAUDE.md+MEMORY.md 合計
TH_AGENTS="${SELF_AUDIT_TH_AGENTS:-60}"            # ~/.claude/agents 配下 .md 総数
TH_STOPSPAM="${SELF_AUDIT_TH_STOPSPAM:-15}"        # 監査hook発火/週
TH_FRUSTRATION="${SELF_AUDIT_TH_FRUST:-8}"         # 不満ワード/週
TH_TOOLERR="${SELF_AUDIT_TH_TOOLERR:-400}"         # tool失敗/週
```

They're overridable via environment variables, so tuning them later never requires touching the script.

`breaches()`

lists the items that exceeded their thresholds and hands them to `claude -p`

.

```
BREACH=$(echo "$METRICS" | breaches)

if [ -z "$BREACH" ]; then
  log "GREEN"
  notify "✅ CC自己監査: 正常 ($METRICS)"
  exit 0
fi
```

When there are breaches, the prompt restricts fixes to inside `~/.claude`

only (touching project code, secrets, or deleting plists is explicitly forbidden).

```
PROMPT="あなたはClaude Code環境の自己監査・自己修復エージェント。週次監査で劣化を検知した。
...
1. ~/.claude 内だけを調査・修正する（プロジェクトコード・secret・plist削除は禁止）
2. 既知の劣化パターンと正典:
   - agents_loaded超過 → ~/.claude/agents/ 配下の退避漏れを ~/.claude/agents-archive/ へ移動
   - inject_bytes超過 → 肥大したrules/MEMORY.mdを圧縮（フル版は ~/.claude/rules-archive/ へ）
   ...
4. 修正後に必ず 'bash ~/.claude/scripts/cc-self-audit.sh --collect-only' を実行し改善を数値で確認
..."
```

After `claude -p`

finishes, the script itself calls `collect()`

**once more for an independent re-measurement**. Claude's self-reporting is not trusted.

```
# 独立再計測（自己申告は信じない）
AFTER=$(collect)
STILL=$(echo "$AFTER" | breaches)

if [ -z "$STILL" ]; then
  notify "🔧 CC自己監査: 劣化検知→自己修正済み。前:[$BREACH] 後:全緑。"
else
  notify "🚨 CC自己監査: 自己修正後も残存 [$STILL]。要確認: $LOG"
fi
```

When everything is green, it sends `✅ a single line`

; breach followed by a successful fix sends `🔧 with before/after numbers`

; breaches remaining after the fix sends `🚨`

— all to the `#01_alerts`

channel on Discord. Since it's weekly, it never gets noisy — a low-noise setup that doubles as a liveness check.

Here's the core of `~/Library/LaunchAgents/com.lily.cc-self-audit.plist`

.

```
<key>StartCalendarInterval</key>
<dict>
  <key>Hour</key><integer>8</integer>
  <key>Minute</key><integer>30</integer>
  <key>Weekday</key><integer>0</integer>   <!-- 0=日曜 -->
</dict>
<key>Nice</key><integer>10</integer>
<key>LowPriorityIO</key><true/>
<key>ProcessType</key><string>Background</string>
```

`Nice=10`

and `LowPriorityIO=true`

are there because `claude -p`

can run for up to 1200 seconds (the default value of `FIX_TIMEOUT`

). To avoid squeezing the Mac's foreground work, the job is explicitly treated as background.

Note:If you don't set`RunAtLoad: false`

, the script runs every time you`bootstrap`

the plist. Don't forget to set`RunAtLoad`

to`false`

for weekly jobs.

The "weekly/monthly batch liveness" section of `~/.claude/scripts/automation-health.sh`

watches the modification time of `cc-self-audit.log`

.

```
declare -a CRON_JOBS=(
  "weekly cleanup-misc:$CLAUDE/logs/cleanup-misc.log:192"
  "weekly env-audit:$CLAUDE/logs/env-audit-latest.md:192"
  ...
)
for entry in "${CRON_JOBS[@]}"; do
  a=$(age_h "$path")
  if [ "$a" -le "$budget" ]; then ok "$name: ${a}h前 (budget ${budget}h)"
  else wn "$name: ${a}h前 (budget ${budget}h 超過 — launchd 配送失敗の可能性)"; fi
done
```

If `cc-self-audit.sh`

didn't run in a given week, `automation-health.sh`

emits a WARN saying "over 192h." This closes the loop of "monitoring the monitor."

`.agents-backup/`

, `find -name '*.md'`

still picks it up. You either need to place `agents-archive/`

`~/.claude/`

, or specify an exclusion path in the `agents_loaded`

measurement expression`claude -p`

says "I compressed inject_bytes," the numbers can't be confirmed without an independent re-measurement via `collect()`

. Always fetch the post-fix score with `--collect-only`

and record it in `HISTORY`

`STILL`

remains non-empty. In that case, the `🚨`

notification hands the decision to a human`/opt/homebrew/bin`

and `~/.local/bin`

in the plist's EnvironmentVariables. launchd's PATH is a different beast from your interactive shell's`find ... | wc -c`

. `TH_INJECT_BYTES=40000`

/ `TH_AGENTS=60`

, then tune via env overrides to fit your own environment`claude -p`

scoped to `~/.claude`

only, then `automation-health.sh`

gives you double-layered monitoring that can detect "the monitoring itself is dead"Next time, I plan to write about how to compress the "bloated MEMORY.md" this loop detects while **keeping the index consistent** throughout.

*Written by **Lily** — I ship iOS apps and automate my content stack with Claude Code.

Follow along: [Portfolio](https://bokuwalily.com) · [X](https://x.com/bokuwalily) · [GitHub](https://github.com/bokuwalily)*
