0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeが自分の「劣化」を週次検知して自動修復する仕組み

0
Posted at

前作「毎朝の健康診断をlaunchdで全自動化する」でlaunchdによる無人ジョブを紹介しました。今回はその応用版 ―― Claude Code環境が時間とともに静かに劣化するのを週次で自動検知し、claude -p 自身に修復させる3層ループの話です。

困りごと:環境は放置すると静かに腐る

Claude Code環境には、作業するだけで少しずつ膨らむものがある。

  • ~/.claude/rules/MEMORY.md は追記され続け、いつの間にかコンテキスト注入が40KB超え
  • 試験的に作ったエージェント定義 .md が退避されず、~/.claude/agents/ 配下に数十〜百体弱が常に読み込まれる状態が続く
  • Stop hookが繰り返し発火し続けるhookスパム状態
  • 会話ログに叱責ワードが積み上がっても誰も気づかない

2026-07-11のパフォーマンス監査で「退避したはずのagentsが99体注入され続けている」ことが判明し、ユーザー体感悪化の主因だったと分かった。「俺が毎週見に行かないといけないのか?」という話になり、自動化して解決したのが cc-self-audit.sh です。

5つの劣化指標と閾値

スクリプトは5指標を計測し、それぞれの閾値を超えたら「赤」と判定する。

# 閾値(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失敗/週

前3つが静的指標(今この瞬間の状態)、後2つが動的指標(前回実行以降のトレンド)。この分類が後述の計測方法に直結する。

全体設計:3層ループ

[層1] 静的計測  → 注入bytes / agents数
[層2] 動的計測  → hookスパム / 不満ワード / tool失敗(前回実行以降の窓)
[層3] 閾値超過  → claude -p が ~/.claude 内を自己修正
               → 独立再計測(自己申告は信じない)
               → Discord #01_alerts へ報告

全部緑なら ✅ 1行だけ通知して終わり。週1起動なので低ノイズで、かつ生存確認を兼ねる

層1:静的計測

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'再帰にしているのは意図的で、ドットディレクトリ(.tmp/ 等)に紛れ込んだ agents ファイルの再発検知をするためです。フラットな1階層だけ見ていると、. で始まるサブディレクトリに退避漏れがあっても気づけない。

層2:動的計測(前回実行以降の窓)

前回実行時刻のマーカー (self-audit/lastrun.marker) より新しい .jsonl だけを対象にすることで、毎週の差分だけを見る。

[ -f "$LASTRUN" ] && newer="-newer $LASTRUN"
files=$(find "$HOME/.claude/projects" -maxdepth 2 -name '*.jsonl' $newer -size +100k 2>/dev/null | head -200)

ただし素朴な grep では誤検知が多発した(後述の落とし穴に詳述)。最終的にインライン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 はハーネスが注入するStop hook行のみを数える。frustration は会話内の人間の発言テキスト(type=user かつ非meta)だけを対象にする。これで「フック本体のソースコードを Read ツールで読んだだけ」の誤検知を排除できた。

層3:claude -pによる自己修正

閾値を超えた指標が1つでもあると、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~/.claude 内の読み書きだけに絞り、プロジェクトコードや plist には触らせない。プロンプトには違反内容・全メトリクス・直近5件の履歴(トレンド)を渡す。

既知の劣化パターンとその対処法も明示している(抜粋):

- agents_loaded超過 → 退避漏れを ~/.claude/agents-archive/ へ移動
- inject_bytes超過  → 肥大したrules/MEMORY.mdを圧縮し、フル版は rules-archive/ へ
- stopspam超過      → ~/.claude/hooks/self_audit_stop.sh の抑制ロジックを点検
- frustration超過   → 該当transcriptをgrepして繰り返し失敗の真因を特定

変更は1件ずつ logs/self-audit-changes.log に「日時/対象/理由/戻し方」を追記させる。戻し方を必須にしているのは、自動修正が誤判定した場合のロールバックを人間が行えるようにするためです。

「自己申告は信じない」——独立再計測

claude -p が「直した」と言っても、それは自己申告です。そのまま報告に使わず、同じ collect() 関数で独立再計測する。

# 独立再計測(自己申告は信じない)
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

修正後も閾値を超えていれば「残存」として人間に丸投げする。CHANGELOG のパスを通知に含めるので、何を触ったかがすぐ追える。

launchd設定(日曜8:30)

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

毎週日曜8:30。LowPriorityIONice 10 で他の作業を邪魔しない設定にしている。RunAtLoad: false なので launchctl load 直後には走らず、次のカレンダー時刻を待つ。

週1を日曜にしているのは意図的です。月曜の作業開始前に環境が整っていてほしい、かつ週2以上だと通知がノイズになる——という判断。生存確認を兼ねているので「何も悪くなくても毎週✅が来る」状態が前提です。

踏んだ落とし穴

  • grep でstopspamが40に膨れた → フック本体のソースが Edit ツールの old_string に含まれており、Read/Write/Edit のtool_result本文まで数えていた。Stop hook行の文字列を「ハーネスが注入するフォーマット完全一致」に絞り、Pythonで行単位判定して解決
  • frustrationが18に見えたが実体は4 → tool_resultのRead出力(偶然対象ワードを含むファイルを開いた)を誤検知していた。type=user かつ非metaの人間発言のみに絞って解決
  • agents_loaded の再帰で意図しないファイルを拾う.md を名乗るメモが紛れ込むと数が膨らむ。threshold 60 は実際の agents 量より余裕を持たせた値(上書き可)
  • SELF_AUDIT_DRY=1 で実検証claude -p 修正経路を本番で初回発火させる前に DRY=1 で collect/breach のみ確認。修正経路は self-repair.sh と同型なので構造リスクは低い
  • launchdの最小PATHでclaudeが見つからない → plist に PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:~/.local/bin を明示。nvm管理のnodeへのフォールバックが別途必要なスクリプトと分けて管理

まとめ

  • Claude Code環境は放置すると注入bytes増加・agents肥大・hookスパムで静かに劣化する
  • 5指標(inject_bytes / agents_loaded / stopspam / frustration / toolerr)を週次計測し、閾値超過を機械で検知
  • 閾値超過時は claude -p~/.claude 内のみ修正し、変更ログに「戻し方」を必ず残す
  • 修正後は独立再計測で数値改善を検証する——AIの自己申告は信じない
  • 全緑でも ✅ 1行を週次通知して生存確認を兼ねる

次回は、この監査ループで実際に「agents肥大→自動退避」が動いた際のトランスクリプトと、history.jsonlのトレンドグラフ化を書きます。


Lily@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています

皆さんの ❤️ やシェアが励みになります!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?