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が「なんで治らんの」を検知して自分のCLAUDE.mdを直す ― transcript不満シグナル駆動の自己改善ループ

0
Posted at

前作「会話ログからスキル使用状況を抽出する話」の続きで、今度は逆方向 ―― transcriptを読んで環境が劣化していないか検知し、閾値を超えたらclaude -pが自分で直す週次ループの話です。

~/.claude/scripts/cc-self-audit.sh(177行)が日曜8:30にlaunchdで回ります。5つの数値を取って閾値と比較し、緑なら✅1行・赤なら自己修正→独立再計測→Discord報告。実装の7割は「なぜ素朴なgrepが使えないか」 でした。

困りごと:環境劣化を誰も検知していなかった

2026-07-11のパフォーマンス監査で「退避したはずのagentsが99体注入され続けている」「CLAUDE.mdが知らず228KBに膨れていた」「Stopフックが週に数十回飛んでいた」という三重苦が発覚しました。

それぞれは直せます。問題は「直したはずがまた壊れる」サイクルに気づく仕組みがないことでした。人間が週次で目視するのは続かないので、スクリプトにやらせることにしました。

全体設計:5メトリクス × 閾値 × 独立再計測

launchd(日曜 8:30)
  → cc-self-audit.sh
      ① collect()  →  5数値(静的2 + 動的3)
      ② breaches() →  閾値超過を列挙
      ③ 緑 → ✅通知・終了
      ④ 赤 → claude -p(sonnet・MAX枠)が ~/.claude 内を修正
      ⑤ collect() 独立再実行 → 数値改善を確認(自己申告は信じない)
      ⑥ Discord #01_alerts へ報告

閾値(env変数で上書き可):

メトリクス 閾値 変数
inject_bytes 40,000 B SELF_AUDIT_TH_INJECT
agents_loaded 60 個 SELF_AUDIT_TH_AGENTS
stopspam 15 回/週 SELF_AUDIT_TH_STOPSPAM
frustration 8 回/週 SELF_AUDIT_TH_FRUST
toolerr 400 回/週 SELF_AUDIT_TH_TOOLERR

静的計測:バイト数とagents数

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 ~/.claude/agents -name '*.md' に再帰フラグを付けているのは、ドットで始まるサブdir(.backup/ 等)に退避したはずのagentsが再ロードされる事故を再発検知するためです。agents_loaded が60を超えたら「退避漏れが起きている」と見なします。

動的計測:transcriptのJSONLを人力発言だけ読む

ここが設計の核です。「前回実行以降に更新された100KB超のJSONLファイル最大200本」を対象にして、stopspam(監査hookの実発火数)とfrustration(ユーザーの不満ワード数)を集計します。

STOP_MARKER = 'Stop hook feedback:\n[~/.claude/hooks/self_audit_stop.sh]: '
FRUST_RE = re.compile(r'何回も言|いい加減にし|嘘つ|舐めんな|なんで治らん|最悪やろ|頭悪い')

for line in fh:
    if STOP_MARKER in line:
        stopspam += 1
    try:
        o = json.loads(line)
    except Exception:
        continue
    if o.get('type') != 'user' or o.get('isMeta'):
        continue                          # ← ここが肝
    content = (o.get('message') or {}).get('content')
    texts = [content] if isinstance(content, str) else \
        [b.get('text', '') for b in content if isinstance(b, dict) and b.get('type') == 'text'] \
        if isinstance(content, list) else []
    if any(FRUST_RE.search(t) for t in texts):
        frustration += 1

type=user かつ isMeta でないブロックだけを見る、つまり人間が実際に打ったメッセージだけを対象にします。なぜこれが重要かは次の節で説明します。

2026-07-12のfalse-positive事故

最初の実装はこうでした。

# 旧: 素朴なgrep
stopspam=$(echo "$files" | xargs grep -h -c 'self_audit_stop' 2>/dev/null | awk '{s+=$1} END{print s+0}')
frustration=$(echo "$files" | xargs grep -hE -c 'なんで治らん|いい加減' 2>/dev/null | awk '{s+=$1} END{print s+0}')

history.jsonl には当時の実測値が残っています。

{"ts":"2026-07-11 22:37:28","inject_bytes":21798,"agents_loaded":48,"stopspam":6,"frustration":4,"toolerr":2}
{"ts":"2026-07-12 08:30:06","inject_bytes":21966,"agents_loaded":48,"stopspam":40,"frustration":18,"toolerr":67}
{"ts":"2026-07-12 08:40:03","inject_bytes":21966,"agents_loaded":48,"stopspam":58,"frustration":32,"toolerr":68}

07-11の夜は正常(GREEN)。07-12の朝は stopspam が 6→40、frustration が 4→18 に跳ね上がって RED 判定。claude -p が修正を試みて独立再計測すると、さらに 58/32 まで増えていました。

原因は grep がJSONL全体を文字列検索したことです。transcriptには:

  • 修正作業のdiffold_string/new_string にhookスクリプトのソースが丸ごと入る)
  • Readツールで開いたファイルの中身(たまたまフラストレーション単語が含まれるファイル)
  • tool_resultのエコーバック(前回の監査出力そのものを次のセッションが引用)

これらが全部ヒットしていました。stopspam 40件のうち実際のhook発火はほぼゼロ、frustration 18件のうち人間が打ち込んだ不満ワードも数件でした。

素朴な文字列grepはtranscriptに対して使えない。 JSONLの「どの役割の、どのブロックか」を見てから判定しないと、引用・ツール出力・過去ログのエコーがすべてカウントされる。測定設計の解像度が監査の品質を決める。

修正はJSONLパーサーへの切り替えです。type=user かつ isMeta: false(ユーザーが実際に送ったメッセージ)のテキストブロックだけを対象にし、stopspamはharnessが実際に注入する固定フォーマットの行だけを数えます。これで引用・ツール出力・過去ログのエコーをすべて除外できました。

閾値超過 → 自己修正 → 独立再計測

赤のとき claude -p に渡すプロンプトの核心部分:

PROMPT="あなたはClaude Code環境の自己監査・自己修復エージェント。

## 検知した違反
$BREACH

## 指示
1. ~/.claude 内だけを調査・修正する(プロジェクトコード・secret・plist削除は禁止)
2. 変更は1件ずつ ${CHANGELOG} に「日時/対象/理由/戻し方」を追記
3. 修正後に必ず 'bash ~/.claude/scripts/cc-self-audit.sh --collect-only' を実行し改善を数値で確認
4. 自分で安全に直せない項目は無理せず、最後に『要人間: 理由』と1行で出力
5. 最終出力は3行以内の要約のみ"

重要なのはインストラクション3です。claude -p 自身に再計測をさせるのではなく、harness側でも別途 collect() を独立実行して数値を確認します(165行目〜):

# 独立再計測(自己申告は信じない)
AFTER=$(collect)
log "after: $AFTER"
echo "$AFTER" >> "$HISTORY"
STILL=$(echo "$AFTER" | breaches)

「修正した」とAIが言っても数値が改善していなければ 🚨 を送ります。Claude製ツールを使ってClaude自身の環境を直すとき、自己申告だけを信じると事故ります。

実測値の記録

現在の history.jsonl から読み取れること:

  • inject_bytes: 21,798〜21,966 B(閾値40,000の半分以下、GREEN)
  • agents_loaded: 48(閾値60以下、GREEN)
  • 07-12の赤はfalse-positive(grep修正後は正常値に戻る)

初回2エントリが07-11の --collect-only 前後、3〜4エントリが07-12のgrep誤検知とその後の再計測です。週次ジョブとしての本番稼働は08-30以降。

launchd設定

<key>StartCalendarInterval</key><dict>
  <key>Weekday</key><integer>0</integer>  <!-- 日曜 -->
  <key>Hour</key><integer>8</integer>
  <key>Minute</key><integer>30</integer>
</dict>

RunAtLoadfalse。登録時に即走ると初回計測がlaunchd起動直後の「空」状態で終わるためです。最初の基準計測は手動で --collect-only を叩いて history.jsonl に1行入れてから登録します。

踏んだ落とし穴

  • 素朴なgrepがtranscript全域を誤検知 → JSONLパーサーでtype=user && !isMetaのみ対象
  • stopspamのself_audit_stopマッチがhookスクリプト自身の引用にもヒット → harnessが実際に注入する固定マーカー文字列(フォーマット完全一致)だけ数える
  • AIが「修正した」と報告しても数値が変わっていないclaude -p の出力とは独立して collect() を再実行し HISTORY に追記してから判定する
  • launchd の最小PATHでclaudeが見つからない → plistの EnvironmentVariablesPATH を明示(/opt/homebrew/bin + ~/.local/bin が必須)
  • frustrationがゼロに貼り付いて「何も起きていない」に見える → 「不満ゼロ」は吉兆であり、月次で閾値・正規表現が現実のワードセットに追いついているか確認する

まとめ

  • Claude Code環境の劣化(agent過剰・注入肥大・hookスパム・ユーザー不満)を5メトリクスで週次計測し、閾値を超えたらclaude -pが自己修正する
  • 動的計測はJSONLのロール判定(type=user && !isMeta)なしでは誤検知だらけになる ―― transcriptへの素朴なgrepは使えない
  • 自己修正後はAIの自己申告とは独立してharnessが再計測し、数値改善を確認してから通知する
  • 「測定設計の解像度が監査の品質を決める」―― 2026-07-12の事故はこれを実コードで実証してくれました

次回は、この監査が発火したときclaude -pが実際に何をどう直したか、変更ログ(self-audit-changes.log)の実例を追います。


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?