Your Claude Code Setup Gets Bloated If You Ignore It - Weekly Auto-Slimming by Watching Injected Bytes, Agent Count, and Frustration Words
The Problem: 99 "Retired" Agents Were Still Being Injected
This is a real incident I hit during a performance audit on 2026-07-11. I'd tidied up ~/.claude/agents/ several times. I'd manually moved agent definitions I no longer used into agents-archive/ and figured I was done. But when I actually measured, agents_loaded was still stuck at 99. The culprit was dot-directories (.deprecated/, .backup/, and the like). find's default behavior recurses into dot-directories too. They looked deleted, but every single one was still being loaded through the hidden directories. This real incident is what prompted me to write cc-self-audit.sh.
Five Metrics
The script's collect() function grabs five metrics every run.
# ~/.claude/scripts/cc-self-audit.sh(collect関数より)
# --- 静的 ---
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 ' ')
# --- 動的(前回実行以降のtranscriptのみ・最大200ファイル)---
stopspam=$(echo "$files" | xargs /usr/bin/grep -h -c 'セルフ監査未実施。実装' 2>/dev/null | awk '{s+=$1} END{print s+0}')
frustration=$(echo "$files" | xargs /usr/bin/grep -h -c -E '何回も言|いい加減にし|嘘つ|舐めんな|なんで治らん|最悪やろ|頭悪い' 2>/dev/null | awk '{s+=$1} END{print s+0}')
toolerr=$(echo "$files" | xargs /usr/bin/grep -h -o -c '"is_error":true' 2>/dev/null | awk '{s+=$1} END{print s+0}')
| Metric | Meaning | Threshold |
|---|---|---|
inject_bytes |
Total bytes of rules + CLAUDE.md + MEMORY.md | 40,000 |
agents_loaded |
Total .md count under ~/.claude/agents/ (recursion including dot-dirs) | 60 |
stopspam |
Number of times the audit hook fired / week | 15 |
frustration |
Occurrences of frustration words in transcripts / week | 8 |
toolerr |
Count of "is_error":true in transcripts / week |
400 |
The two static metrics are proxy variables for injection cost, and the three dynamic metrics are proxy variables for experience quality. The frustration-word detection may look out of place, but if I'm scolding it repeatedly, that means it's repeating the same failure - and those failures often stem from environment problems (misfiring hooks, rotted memory, broken tools).
The thresholds can be overridden with env variables.
TH_INJECT_BYTES="${SELF_AUDIT_TH_INJECT:-40000}"
TH_AGENTS="${SELF_AUDIT_TH_AGENTS:-60}"
TH_STOPSPAM="${SELF_AUDIT_TH_STOPSPAM:-15}"
TH_FRUSTRATION="${SELF_AUDIT_TH_FRUST:-8}"
TH_TOOLERR="${SELF_AUDIT_TH_TOOLERR:-400}"
When a Threshold Is Exceeded, claude -p Fixes Itself
If every metric is under its threshold, it just posts a one-line ✅ GREEN to Discord and exits. If any is over, it calls claude -p to fix it.
BREACH=$(echo "$METRICS" | breaches)
if [ -z "$BREACH" ]; then
log "GREEN"
touch "$LASTRUN"
notify "✅ CC自己監査: 正常 ($METRICS)"
exit 0
fi
log "RED: $BREACH"
if [ "$DRY" = "1" ]; then
log "DRY=1: 修正スキップ"; touch "$LASTRUN"; exit 0;
fi
The prompt passed to claude -p has a remediation policy embedded for each decay pattern.
PROMPT="...
## 指示
1. ~/.claude 内だけを調査・修正する(プロジェクトコード・secret・plist削除は禁止)
2. 既知の劣化パターンと正典:
- agents_loaded超過 → ~/.claude/agents/ 配下の退避漏れ(ドットdirも読み込まれる)を ~/.claude/agents-archive/ へ移動
- inject_bytes超過 → 肥大したrules/MEMORY.mdを圧縮(フル版は ~/.claude/rules-archive/ へ。リンクは全維持し索引整合を検証)
- stopspam超過 → ~/.claude/hooks/self_audit_stop.sh の抑制ロジックを点検
- frustration/toolerr超過 → 該当transcriptをgrepして繰り返し失敗の真因を特定し、hook/スキル/メモリで再発防止を仕込む
3. 変更は1件ずつ ${CHANGELOG} に「日時/対象/理由/戻し方」を追記
4. 修正後に必ず 'bash ~/.claude/scripts/cc-self-audit.sh --collect-only' を実行し改善を数値で確認
..."
I restrict it to filesystem operations only with --allowedTools "Read,Write,Edit,Bash,Grep,Glob", and cap it at max-turns 50 and a 20-minute wall clock (FIX_TIMEOUT=1200).
Note
Having claude run--collect-onlyitself inside the fix loop is strictly "a confirmation as part of the remediation procedure." The final verdict of the audit is made by the outer, independent re-measurement. The key to the design is not taking the inner confirmation at face value.
A "Don't Trust Self-Reporting" Fail-Closed Design
Even when claude -p says "fixed it," I don't believe it right away. The outer harness calls collect() again to re-measure the numbers.
# 独立再計測(自己申告は信じない)
AFTER=$(collect)
log "after: $AFTER"
echo "$AFTER" >> "$HISTORY"
STILL=$(echo "$AFTER" | breaches)
touch "$LASTRUN"
if [ -z "$STILL" ]; then
notify "🔧 CC自己監査: 劣化検知→自己修正済み。前:[$BREACH] 後:全緑。詳細=$CHANGELOG"
else
notify "🚨 CC自己監査: 自己修正後も残存 [$STILL]。要確認: $LOG / $CHANGELOG"
fi
If a violation still remains after the fix, it flies off to Discord with a 🚨 and hands it over to a human. Trust only the OS's measured values, not self-reporting - that's the core of fail-closed.
All measured values are stacked one record per line in ~/.claude/self-audit/history.jsonl, and the prompt handed to claude also includes the most recent 5 entries as a trend. By looking at the magnitude of change - "it was 30KB last week but 51KB this week" - I can distinguish a one-off outlier from a continuous increase.
Weekly Launch via launchd, Sundays at 8:30
<!-- ~/Library/LaunchAgents/com.lily.cc-self-audit.plist -->
<key>StartCalendarInterval</key><dict>
<key>Weekday</key><integer>0</integer>
<key>Hour</key><integer>8</integer>
<key>Minute</key><integer>30</integer>
</dict>
<key>RunAtLoad</key><false/>
Weekday=0 is Sunday. I set RunAtLoad to false to prevent it from running immediately right after registering with launchd. I include ~/.local/bin in the environment variable PATH and place a claude shim there.
There are two modes for manual runs.
# 計測だけ(claudeを呼ばない)
~/.claude/scripts/cc-self-audit.sh --collect-only
# ドライラン(違反表示のみ・修正なし)
SELF_AUDIT_DRY=1 ~/.claude/scripts/cc-self-audit.sh
Before putting it into production, I always eyeballed the metrics with DRY=1 first, then enabled it.
Pitfalls I Hit
- Dot-dirs get caught by find →
~/.claude/agents/.deprecated/and the like became recursion targets, so agents I thought I'd retired kept getting counted. The root cause of the real incident. - The transcript find is slow → Unless you cap the number of files with
head -200and exclude empty files with-size +100k, the first run of the weekend takes over 3 minutes. - The frustration-word regex mixes in Japanese → Unless you set
LANG=en_US.UTF-8at the top,grep -Emisbehaves on multibyte characters. - claude -p isn't on the PATH → launchd's minimal PATH was missing
~/.local/bin. Solved by explicitly adding it to the plist'sEnvironmentVariables. - Making claude do the post-fix re-measurement internally turns it into self-grading → The outer
collect()call is mandatory. The inner confirmation stays limited to "part of the remediation procedure."
Warning
As the comment says - bash 3.2 compatible, fail-open - the audit script itself is designed so that nothing breaks even if it crashes. It uses set -uo pipefail, each individual measurement swallows errors with 2>/dev/null || true, and in the worst case the measured value becomes 0 and gets treated as "GREEN." Having the main system die because of an audit failure would be putting the cart before the horse.
Summary
A Claude Code setup accumulates decay that, if left alone, can be measured across five metrics: inject_bytes, agent count, hook spam, frustration words, and tool failures. When a threshold is exceeded, claude -p fixes only what's inside ~/.claude, and records it in the change log (self-audit-changes.log) with a "how to revert" note. After the fix, an outer independent re-measurement verifies the improvement numerically. A self-reported "fixed it" alone is not treated as done. Weekly launch via launchd, Sundays at 8:30. You can manually check with --collect-only and DRY=1. The script itself is fail-open (an audit failure won't break the environment), while the target of self-repair is fail-closed (if a violation remains after the fix, it escalates to a human).
Next time, I'll write about how I trimmed down the inject_bytes overage that this audit loop detected - a pipeline that compresses the rules and auto-slims them every week.
Written by Lily - I ship iOS apps and automate my content stack with Claude Code. Follow along: Portfolio · X · GitHub
Comments
No comments yet. Start the discussion.