{"slug": "the-bigger-your-claude-md-gets-the-slower-claude-code-runs-measuring-context-to", "title": "The Bigger Your CLAUDE.md Gets, the Slower Claude Code Runs — Measuring Context Injection Bytes Weekly to Catch Degradation Early", "summary": "A developer found that bloated CLAUDE.md files and orphaned agent configurations were causing Claude Code to run slowly, with context injection exceeding 40KB and 99 archived agents still being loaded. To catch degradation early, they built a weekly self-audit script that measures injection bytes, agent count, stop-hook spam, frustration keywords, and tool errors, automatically attempting fixes when thresholds are breached.", "body_md": "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**.\n\nAt the start of every session, Claude Code unconditionally injects the full contents of `rules/*.md`\n\n, `~/.claude/CLAUDE.md`\n\n, `~/CLAUDE.md`\n\n, and `MEMORY.md`\n\n. 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.\n\nAdding 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/`\n\nkeep getting loaded anyway.\n\nThe 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.\n\nThe `collect()`\n\nfunction in `~/.claude/scripts/cc-self-audit.sh`\n\nassembles the metrics. There are two axes: static and dynamic measurement.\n\n```\ninject_bytes=$(( \\\n  $(find \"$HOME/.claude/rules\" -name '*.md' -print0 2>/dev/null | xargs -0 cat 2>/dev/null | wc -c) + \\\n  $(cat \"$HOME/.claude/CLAUDE.md\" 2>/dev/null | wc -c) + \\\n  $(cat \"$HOME/CLAUDE.md\" 2>/dev/null | wc -c) + \\\n  $(cat \"$HOME/.claude/projects/-Users-matsubara/memory/MEMORY.md\" 2>/dev/null | wc -c) ))\nagents_loaded=$(find \"$HOME/.claude/agents\" -name '*.md' 2>/dev/null | wc -l | tr -d ' ')\n```\n\n`find ... -name '*.md'`\n\nrecurses into dot directories too. Even if you think \"I moved them to `agents-archive/`\n\n, so it's fine,\" if that directory lives under `~/.claude/agents/`\n\nas a dot directory, the files **keep getting loaded** — that's the trap (this is exactly how 99 agents survived on 2026-07-11).\n\nThree metrics are aggregated from the transcripts (`.jsonl`\n\n) since the last run.\n\n```\nSTOP_MARKER = 'Stop hook feedback:\\\\n[~/.claude/hooks/self_audit_stop.sh]: '\nFRUST_RE = re.compile(r'何回も言|いい加減にし|嘘つ|舐めんな|なんで治らん|最悪やろ|頭悪い')\n```\n\nstopspam 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`\n\nthat are not `isMeta`\n\n— i.e., the human's own raw utterances — are counted.\n\n```\nTH_INJECT_BYTES=\"${SELF_AUDIT_TH_INJECT:-40000}\"   # rules+CLAUDE.md+MEMORY.md 合計\nTH_AGENTS=\"${SELF_AUDIT_TH_AGENTS:-60}\"            # ~/.claude/agents 配下 .md 総数\nTH_STOPSPAM=\"${SELF_AUDIT_TH_STOPSPAM:-15}\"        # 監査hook発火/週\nTH_FRUSTRATION=\"${SELF_AUDIT_TH_FRUST:-8}\"         # 不満ワード/週\nTH_TOOLERR=\"${SELF_AUDIT_TH_TOOLERR:-400}\"         # tool失敗/週\n```\n\nThey're overridable via environment variables, so tuning them later never requires touching the script.\n\n`breaches()`\n\nlists the items that exceeded their thresholds and hands them to `claude -p`\n\n.\n\n```\nBREACH=$(echo \"$METRICS\" | breaches)\n\nif [ -z \"$BREACH\" ]; then\n  log \"GREEN\"\n  notify \"✅ CC自己監査: 正常 ($METRICS)\"\n  exit 0\nfi\n```\n\nWhen there are breaches, the prompt restricts fixes to inside `~/.claude`\n\nonly (touching project code, secrets, or deleting plists is explicitly forbidden).\n\n```\nPROMPT=\"あなたはClaude Code環境の自己監査・自己修復エージェント。週次監査で劣化を検知した。\n...\n1. ~/.claude 内だけを調査・修正する（プロジェクトコード・secret・plist削除は禁止）\n2. 既知の劣化パターンと正典:\n   - agents_loaded超過 → ~/.claude/agents/ 配下の退避漏れを ~/.claude/agents-archive/ へ移動\n   - inject_bytes超過 → 肥大したrules/MEMORY.mdを圧縮（フル版は ~/.claude/rules-archive/ へ）\n   ...\n4. 修正後に必ず 'bash ~/.claude/scripts/cc-self-audit.sh --collect-only' を実行し改善を数値で確認\n...\"\n```\n\nAfter `claude -p`\n\nfinishes, the script itself calls `collect()`\n\n**once more for an independent re-measurement**. Claude's self-reporting is not trusted.\n\n```\n# 独立再計測（自己申告は信じない）\nAFTER=$(collect)\nSTILL=$(echo \"$AFTER\" | breaches)\n\nif [ -z \"$STILL\" ]; then\n  notify \"🔧 CC自己監査: 劣化検知→自己修正済み。前:[$BREACH] 後:全緑。\"\nelse\n  notify \"🚨 CC自己監査: 自己修正後も残存 [$STILL]。要確認: $LOG\"\nfi\n```\n\nWhen everything is green, it sends `✅ a single line`\n\n; breach followed by a successful fix sends `🔧 with before/after numbers`\n\n; breaches remaining after the fix sends `🚨`\n\n— all to the `#01_alerts`\n\nchannel on Discord. Since it's weekly, it never gets noisy — a low-noise setup that doubles as a liveness check.\n\nHere's the core of `~/Library/LaunchAgents/com.lily.cc-self-audit.plist`\n\n.\n\n```\n<key>StartCalendarInterval</key>\n<dict>\n  <key>Hour</key><integer>8</integer>\n  <key>Minute</key><integer>30</integer>\n  <key>Weekday</key><integer>0</integer>   <!-- 0=日曜 -->\n</dict>\n<key>Nice</key><integer>10</integer>\n<key>LowPriorityIO</key><true/>\n<key>ProcessType</key><string>Background</string>\n```\n\n`Nice=10`\n\nand `LowPriorityIO=true`\n\nare there because `claude -p`\n\ncan run for up to 1200 seconds (the default value of `FIX_TIMEOUT`\n\n). To avoid squeezing the Mac's foreground work, the job is explicitly treated as background.\n\nNote:If you don't set`RunAtLoad: false`\n\n, the script runs every time you`bootstrap`\n\nthe plist. Don't forget to set`RunAtLoad`\n\nto`false`\n\nfor weekly jobs.\n\nThe \"weekly/monthly batch liveness\" section of `~/.claude/scripts/automation-health.sh`\n\nwatches the modification time of `cc-self-audit.log`\n\n.\n\n```\ndeclare -a CRON_JOBS=(\n  \"weekly cleanup-misc:$CLAUDE/logs/cleanup-misc.log:192\"\n  \"weekly env-audit:$CLAUDE/logs/env-audit-latest.md:192\"\n  ...\n)\nfor entry in \"${CRON_JOBS[@]}\"; do\n  a=$(age_h \"$path\")\n  if [ \"$a\" -le \"$budget\" ]; then ok \"$name: ${a}h前 (budget ${budget}h)\"\n  else wn \"$name: ${a}h前 (budget ${budget}h 超過 — launchd 配送失敗の可能性)\"; fi\ndone\n```\n\nIf `cc-self-audit.sh`\n\ndidn't run in a given week, `automation-health.sh`\n\nemits a WARN saying \"over 192h.\" This closes the loop of \"monitoring the monitor.\"\n\n`.agents-backup/`\n\n, `find -name '*.md'`\n\nstill picks it up. You either need to place `agents-archive/`\n\n`~/.claude/`\n\n, or specify an exclusion path in the `agents_loaded`\n\nmeasurement expression`claude -p`\n\nsays \"I compressed inject_bytes,\" the numbers can't be confirmed without an independent re-measurement via `collect()`\n\n. Always fetch the post-fix score with `--collect-only`\n\nand record it in `HISTORY`\n\n`STILL`\n\nremains non-empty. In that case, the `🚨`\n\nnotification hands the decision to a human`/opt/homebrew/bin`\n\nand `~/.local/bin`\n\nin the plist's EnvironmentVariables. launchd's PATH is a different beast from your interactive shell's`find ... | wc -c`\n\n. `TH_INJECT_BYTES=40000`\n\n/ `TH_AGENTS=60`\n\n, then tune via env overrides to fit your own environment`claude -p`\n\nscoped to `~/.claude`\n\nonly, then `automation-health.sh`\n\ngives 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.\n\n*Written by **Lily** — I ship iOS apps and automate my content stack with Claude Code.\n\nFollow along: [Portfolio](https://bokuwalily.com) · [X](https://x.com/bokuwalily) · [GitHub](https://github.com/bokuwalily)*", "url": "https://wpnews.pro/news/the-bigger-your-claude-md-gets-the-slower-claude-code-runs-measuring-context-to", "canonical_source": "https://dev.to/bokuwalily/the-bigger-your-claudemd-gets-the-slower-claude-code-runs-measuring-context-injection-bytes-j84", "published_at": "2026-07-24 11:00:06+00:00", "updated_at": "2026-07-24 11:33:27.837549+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "large-language-models", "mlops"], "entities": ["Claude Code"], "alternates": {"html": "https://wpnews.pro/news/the-bigger-your-claude-md-gets-the-slower-claude-code-runs-measuring-context-to", "markdown": "https://wpnews.pro/news/the-bigger-your-claude-md-gets-the-slower-claude-code-runs-measuring-context-to.md", "text": "https://wpnews.pro/news/the-bigger-your-claude-md-gets-the-slower-claude-code-runs-measuring-context-to.txt", "jsonld": "https://wpnews.pro/news/the-bigger-your-claude-md-gets-the-slower-claude-code-runs-measuring-context-to.jsonld"}}