How Claude Code Detects Its Own Weekly Rot and Repairs Itself A developer automated a weekly self-audit for Claude Code that detects silent degradation—such as bloated injected context and orphaned agent files—and hands the repair job to claude -p itself. The three-layer script measures five metrics, flags red when thresholds are crossed, and sends a single ✅ line if all is green, doubling as a low-noise liveness check. Your Claude Code setup doesn't break in one dramatic moment — it degrades a few bytes at a time, and by the time you notice, you've been paying a context tax for weeks. In a previous post https://zenn.dev/bokuwalily/articles/daily-brief-ops-dashboard I covered running an unattended daily health check with launchd. This one is the follow-up: a three-layer loop that detects that quiet degradation weekly and hands the repair job to claude -p itself. Some things in a Claude Code environment grow just from doing your normal work. ~/.claude/rules/ and MEMORY.md keep getting appended to, until .md files never get archived, leaving ~/.claude/agents/ permanently loadedA performance audit on 2026-07-11 revealed that "agents I thought I'd archived were still being injected — 99 of them," and that turned out to be the main cause of the degraded experience. That led to the question "so do I have to go check this every week myself?" — and the answer was to automate it , which is what cc-self-audit.sh does. The script measures five metrics and flags "red" when any of them crosses its threshold. 閾値(env変数で上書き可) 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失敗/週 The first three are static metrics state at this exact moment ; the last two are dynamic metrics trends since the previous run . That distinction maps directly onto how each one is measured, as described below. 層1 静的計測 → 注入bytes / agents数 層2 動的計測 → hookスパム / 不満ワード / tool失敗(前回実行以降の窓) 層3 閾値超過 → claude -p が ~/.claude 内を自己修正 → 独立再計測(自己申告は信じない) → Discord 01 alerts へ報告 If everything is green, it sends a single ✅ line and exits. Since it only runs once a week, it stays low-noise while also doubling as a liveness check . 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 ' ' Making find … -name ' .md' recursive is deliberate: it catches agent files that have snuck into dot-directories .tmp/ and friends so the problem can be detected if it recurs. If you only look at a single flat level, you'll never notice files that failed to get archived out of a subdirectory starting with . . By only targeting .jsonl files newer than the marker for the previous run time self-audit/lastrun.marker , it looks at only that week's delta . -f "$LASTRUN" && newer="-newer $LASTRUN" files=$ find "$HOME/.claude/projects" -maxdepth 2 -name ' .jsonl' $newer -size +100k 2 /dev/null | head -200 However, a naive grep produced a lot of false positives detailed in the pitfalls section below . In the end I improved precision with inline Python. STOP MARKER = 'Stop hook feedback:\\n ~/.claude/hooks/self audit stop.sh : ' FRUST RE = re.compile r'何回も言|いい加減にし|嘘つ|舐めんな|なんで治らん|最悪やろ|頭悪い' stopspam: harnessが実際に注入する行のみ if STOP MARKER in line: stopspam += 1 frustration: isMeta でない type=user のテキストブロック(人間の実発言)のみ if o.get 'type' = 'user' or o.get 'isMeta' : continue content = o.get 'message' or {} .get 'content' ... テキスト抽出して FRUST RE で判定 stopspam counts only the Stop hook lines injected by the harness. frustration targets only human utterance text within the conversation type=user and non-meta . That eliminated false positives from cases like "I just read the hook's own source code with the Read tool." If even one metric crosses its threshold, a repair prompt is piped into claude -p . OUT=$ cd "$HOME/.claude" && printf '%s' "$PROMPT" | run capped "$FIX TIMEOUT" "$CLAUDE" -p \ --model "$MODEL" --output-format text \ --allowedTools "Read,Write,Edit,Bash,Grep,Glob" \ --max-turns 50 2 &1 || true --allowedTools narrows it to reading and writing inside ~/.claude only — no touching project code or plists. The prompt is handed the violations, all metrics, and the last five history entries the trend . Known degradation patterns and their remedies are spelled out too excerpt : - agents loaded超過 → 退避漏れを ~/.claude/agents-archive/ へ移動 - inject bytes超過 → 肥大したrules/MEMORY.mdを圧縮し、フル版は rules-archive/ へ - stopspam超過 → ~/.claude/hooks/self audit stop.sh の抑制ロジックを点検 - frustration超過 → 該当transcriptをgrepして繰り返し失敗の真因を特定 Each change gets appended one at a time to logs/self-audit-changes.log with "timestamp / target / reason / how to revert ". Requiring the revert instructions is what lets a human roll back when the automatic repair makes a bad call. When claude -p says "fixed it," that's self-reporting. Rather than using that directly in the report, it re-measures independently with the same collect function . 独立再計測(自己申告は信じない) AFTER=$ collect log "after: $AFTER" echo "$AFTER" "$HISTORY" STILL=$ echo "$AFTER" | breaches if -z "$STILL" ; then notify "🔧 CC自己監査: 劣化検知→自己修正済み。前: $BREACH 後:全緑。詳細=$CHANGELOG" else notify "🚨 CC自己監査: 自己修正後も残存 $STILL 。要確認: $LOG / $CHANGELOG Claude要約: $ echo "$OUT" | tail -3 | tr '\n' ' ' " fi If a threshold is still exceeded after the repair, it's marked as "remaining" and thrown back to the human. Since the notification includes the CHANGELOG path, you can immediately trace what was touched.