cd /news/ai-tools/how-claude-code-detects-its-own-week… · home topics ai-tools article
[ARTICLE · art-73828] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=· neutral

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.

read6 min views1 publishedJul 26, 2026

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 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.

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'何回も言|いい加減にし|嘘つ|舐めんな|なんで治らん|最悪やろ|頭悪い')

if STOP_MARKER in line:
    stopspam += 1

if o.get('type') != 'user' or o.get('isMeta'):
    continue
content = (o.get('message') or {}).get('content')

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.

<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>LowPriorityIO</key>  <true/>
<key>Nice</key>           <integer>10</integer>
<key>ProcessType</key>    <string>Background</string>

Every Sunday at 8:30. LowPriorityIO

and Nice 10

keep it from getting in the way of other work. Since RunAtLoad: false

, it doesn't fire immediately after launchctl load

— it waits for the next calendar time.

Note

Putting the weekly run on Sunday is deliberate: I want the environment in good shape before Monday's work starts, and running it twice a week or more would turn the notifications into noise. Because it doubles as a liveness check, the whole premise is that "a ✅ arrives every week even when nothing is wrong."

grep

inflated stopspam to 40old_string

of an Edit

tool call, so it was counting the body of Read/Write/Edit tool_results too. Fixed by narrowing the Stop hook line to an "exact match on the format the harness injects" and doing line-level checks in Pythontype=user

and non-metaagents_loaded

picks up unintended files.md

sneak in, the count balloons. The threshold of 60 is a value with headroom above the actual agent count (and can be overridden)claude -p

repair path fire for the first time in production, I confirmed only collect/breach with DRY=1

. The repair path is structurally identical to self-repair.sh, so structural risk is lowPATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:~/.local/bin

. Managed separately from scripts that additionally need a fallback to nvm-managed nodeclaude -p

repairs only what's inside ~/.claude

, and always records "how to revert" in the change logNext time I'll write about the transcript from an actual run where "agent bloat → automatic archiving" fired in this audit loop, plus turning history.jsonl into a trend graph.

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

Follow along: Portfolio · X · GitHub*

── more in #ai-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-claude-code-dete…] indexed:0 read:6min 2026-07-26 ·