毎朝起きたら、昨夜のうちに自動化が静かに壊れていた——そんな経験、ありませんか。Lilyはここ数ヶ月、記事の自動生成・データ収集・メール仕分けなど複数のスクリプトをMac上で動かし続けています。問題は「壊れること」ではなく、「壊れたことに気づかないまま数日が過ぎてしまうこと」でした。ログを見れば分かるのですが、毎日手動で確認するのでは自動化の恩恵が半減します。
対策として最初に思いつくのは「壊れたらDiscordに通知を飛ばす」という方法です。でもそれだと、通知を受けたLily自身が手動で直すしかありません。もう一歩踏み込んで「Claudeに修復させ、直ったかどうかを別のスクリプトが独立に検証する」という仕組みを作りました。肝心なのは、Claudeがどれだけうまくやったつもりになっていても信用しないという設計思想です。
この記事では、その仕組みの全体像と実装の要点を、できるだけ平易な言葉で解説します。対象読者はエンジニアではないけれどAIや個人開発の自動化に興味がある方です。専門用語が出てくるたびに噛み砕いた説明を入れますので、「自分には難しそう」と思った方もぜひ最後まで読んでみてください。この記事を読むと、①Macでスクリプトを定時実行する方法、②ClaudeにAIとして修復作業をさせる方法、③修復結果をAIの自己申告ではなく独立したスクリプトで検証する方法、④修復が失敗したときにコードを確実に元の状態へ巻き戻す方法、の4点が具体的にわかります。
何を作ったか——ひと言で言うと
一言でいえば、「壊れた自動化を毎日3回点検し、問題があればClaudeに直させ、本当に直ったかを独立したスクリプトが判定し、失敗したら元に戻す」仕組みです。
実装の中心は `~/.claude/scripts/self-repair.sh` という1本のシェルスクリプトです。シェルスクリプトとはMacやLinuxで動くテキスト形式のプログラムで、コマンドを順番に書いたメモ帳のようなものと思ってください。これがMacの定時実行の仕組み(launchd)によって1日3回(8:30・13:30・19:30)に自動起動され、登録済みの自動化を1つひとつ点検します。
異常を検知したら、Claudeに「この自動化が壊れています。直してください」というプロンプト(指示文)を渡します。Claudeが修復を試みた後、self-repair.sh自身が独立したスクリプト(verify.sh)を別に実行して結果を確認します。検証が通ればコードの変更をGit(変更履歴の管理システム)にコミットして本番に反映し、検証が失敗すれば `git reset --hard` ですべての変更を巻き戻します。
なぜ「独立した検証」が必要なのか
AIの回答は、実際には動いていなくても「修正しました」と述べることがあります。これはAIを批判しているのではなく、生成AIの仕組み上、「動いているはず」という推論結果を出力することがあるという特性です。無人で本番に反映する以上、Claudeの自己申告を鵜呑みにするのは危険です。だからこそ、Claudeとは別のプロセスがverify.shを独立して実行するという設計になっています。
全体構成:登場ファイルは4種類
仕組みを支えるファイルは主に4種類です。
加えて `~/.claude/scripts/token-budget-advisor.sh` が、Claudeの直近5時間のAPI使用量を計算し、予算上限を超えていれば修復をスキップする役割を担っています。
スラッグという考え方
各自動化には `affiliate-factory` や `hosei-ingest` のような短い英小文字の名前(スラッグ)をつけます。すべてのファイル名・ログ・通知にこのスラッグが使われるため、どの自動化の話かがどこからでもすぐ特定できます。
launchdでMacに定時実行させる
「定時実行」と聞いてWindowsのタスクスケジューラや、エンジニアに馴染みのあるcronを思い浮かべる方もいるかもしれません。Macにはlaunchd(ローンチディー)という専用の仕組みがあります。設定ファイル(plist)を1つ書いて所定の場所に置くだけで、指定した時刻にコマンドを自動実行してくれます。
自己修復システム用のplistは `~/Library/LaunchAgents/com.shun.self-repair.plist` に置いています。中身の核心部分はこうなっています。
<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>30</integer></dict>
<dict><key>Hour</key><integer>13</integer><key>Minute</key><integer>30</integer></dict>
<dict><key>Hour</key><integer>19</integer><key>Minute</key><integer>30</integer></dict>
</array>これで毎日8:30・13:30・19:30の3回、self-repair.shが自動起動します。plistを登録するコマンドは以下のとおりです。
launchctl load ~/Library/LaunchAgents/com.shun.self-repair.plist実行ログは `~/.claude/logs/self-repair.launchd.log` と `self-repair.launchd.err.log` の2ファイルに書き出されます。`RunAtLoad` が `false` に設定されているため、Macを再起動しても起動直後には動かず、次の予定時刻まで静かに待機します。
タイミングの意図
なぜ8:30・13:30・19:30かというと、既存の主要な自動化(affiliate-factory)が7:30・12:30・18:30に動くからです。その約1時間後に点検することで、当日の実行結果をもとに健全性を判断できます。自動化の実行タイミングと点検タイミングを意図的にずらすのがポイントです。
registry.tsv——監視台帳の設計
どの自動化を監視するかは `~/.claude/self-repair/registry.tsv` に一覧として管理します。TSVとはタブ区切りのテキストファイルで、ExcelのCSVに似た形式です。実際の内容はこうなっています。
# slug project_dir mode
affiliate-factory ~/dev/affiliate-factory full
hosei-ingest ~/hosei-grad-planner full
article-daily ~/.claude detect
lead-outreach ~/lead-finder detect3列の意味は次のとおりです。
- slug:自動化の短縮名。ログや通知の識別子として使います。
- project_dir:コードが置いてあるディレクトリ。Claudeはここ以外には触れません。
- mode:`full`(自動修復あり)か `detect`(検知と通知のみ)の2択です。
fullモードとdetectモードの使い分け
`full` は「Claudeに修復させてよい」完全自律モードです。`affiliate-factory` と `hosei-ingest` はGitで管理されており、副作用が限定的なため full にしています。一方 `article-daily` や `lead-outreach` は外部への書き込みを伴う可能性があるため、検知だけして人間に通知する `detect` にとどめています。
コメント行(`#` で始まる行)は読み飛ばされます。引退した自動化はコメントアウトして記録として残すため、台帳に残り続けます。かつて監視していた `zenn-daily` は2026年6月28日に退役し、その理由もコメントに記録されています。
Claudeへの修復依頼の組み立て方
self-repair.shがClaudeを呼び出すとき、プロンプトを動的に組み立てます。実際のスクリプトから引用すると、核心部分はこうなっています。
local PROMPT="あなたは自律修復エージェントです。自動化『${slug}』(ディレクトリ: ${dir})が壊れています。原因を特定し、**最小の変更**で直してください。
# 制約(厳守)
- 触ってよいのは $dir 配下のファイルのみ。スコープを広げない。新機能を足さない。
- .env や秘密情報を読み出して出力・コミットしない。
- 直したら必ず自分で検証コマンド bash $verify を実行し、exit 0 になることを確認してから終了する。
- 直せない/原因が $dir 外にあるなら『UNFIXABLE: <理由>』とだけ述べて終了する。"このプロンプトには、health.shの出力(何が壊れているかの生の情報)と、直近ログの末尾40行が自動で付加されます。壊れている状況の生データをそのままClaudeに渡すことで、的外れな修復を減らしています。
Claudeの呼び出し方
Claudeをコマンドラインから呼び出す際は `claude -p`(プリントモード:1回回答して終了する非対話モード)を使います。重要な引数は2つです。
"$CLAUDE" -p \
--model claude-sonnet-4-6 \
--allowedTools "Read,Write,Edit,Bash,Grep,Glob" \
--max-turns 40`--allowedTools` は「Claudeが使ってよいツール」の許可リストです。ファイルの読み書き・シェルの実行・ファイル検索だけを許可し、それ以外の操作は締め出しています。`--max-turns 40` は会話のターン数の上限で、Claudeが終わらない試行を繰り返し続けることを防ぎます。さらに `timeout` 相当の処理が外側に設定されており、修復の壁時計時間は最大1800秒(30分)に制限されています。
7つのガードレール——無人で動かすために欠かせない安全装置
自動化がコードを自動で書き換えてGitにプッシュするという行為は、適切な制限がなければ大きなリスクになります。self-repair.shのコメントには「安全の本体はガードレール(無人でコード編集+pushする以上、ここが全て)」と明記されています。実装されている7つのガードレールを1つずつ見ていきます。
ガードレール1:独立検証(Claudeの自己申告を信じない)
修復後、self-repair.shは `bash <slug>-verify.sh` を自分で直接実行します。Claudeが「検証しました」と言っても、ウォッチドッグが独立に確認します。verify.shがexit 0(成功)を返さない限り、コミットもプッシュも行いません。ここがこの仕組みの最重要ポイントです。
ガードレール2:失敗したら即巻き戻し
verify.shが失敗した場合、以下の2行で確実に元の状態に戻します。
git reset -q --hard "${baseline}" 2>/dev/null || true
git clean -fdq 2>/dev/null || true`baseline` は修復開始前のGitコミットのハッシュ値(コードの「バージョン番号」のようなもの)です。未コミットの変更があった場合は修復開始前に `git stash -u` で退避させておき、巻き戻し後に状態が汚れないようにしています。Gitで管理されていないプロジェクトには、この巻き戻しができないため自動修復を適用しません。
ガードレール3:秘密情報の走査(push前スキャン)
Gitにプッシュする前に、変更差分にAPIキーやパスワードが混入していないかを自前でスキャンします。チェックするパターンの例です。
grep -nE \
'pk_[A-Za-z0-9]{6,}|sk_live_[A-Za-z0-9]|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20}|AIza[0-9A-Za-z_-]{20}'StripeのAPIキー(`pk_` / `sk_live_`)、AWSのアクセスキー(`AKIA`始まり)、GitHubのパーソナルアクセストークン(`ghp_`)、GCPのAPIキー(`AIza`)などが対象です。また `.env` ファイルがGit管理下に入っていないかも確認します。ヒットした場合はプッシュを中断して人間に通知します。launchdから動くプロセスではClaudeのsecretスキャンhookが発火しないため、自前で実装しています。
ガードレール4:試行回数の上限
同一スラッグは1日あたり最大2回(`MAX_ATTEMPTS=2`)しか修復を試みません。
MAX_ATTEMPTS="${SELF_REPAIR_MAX_ATTEMPTS:-2}"これにより、修復と失敗を繰り返す無限ループや、APIコストの暴走を防ぎます。上限に達したときは状態ファイル(`state/<slug>-<日付>.gaveup`)を作成し、Discord通知を1回だけ送って人間に手動確認を促します。
ガードレール5:コスト上限(5時間ブロックのトークン数監視)
`~/.claude/scripts/token-budget-advisor.sh` がClaudeの直近5時間の出力トークン数を計算し、`BUDGET_CAP_TOK=380000`(38万トークン)を超えていれば修復の実行をスキップします。
budget_ok() {
tok=$("$BUDGET_ADVISOR" 2>/dev/null | python3 -c 'import sys,json
try: print(int(json.load(sys.stdin).get("5h_output_tokens",0)))
except Exception: print(0)' 2>/dev/null)
if [ "$tok" -gt "$BUDGET_CAP_TOK" ]; then
log "SKIP(budget): 5h_output_tokens=$tok > cap=$BUDGET_CAP_TOK"
return 1
fi
}token-budget-advisorは `ccusage blocks --json` コマンドが使える環境ではそちらの値を優先し、なければ自前のログファイル(`~/.claude/logs/cost-log.jsonl`)から集計します。トークン上限は環境変数 `SELF_REPAIR_BUDGET_CAP` で上書きできるため、使用量が多い時期には閾値を下げることも容易です。
ガードレール6:スコープ制限
Claudeへのプロンプトに「触ってよいのは `$dir` 配下のファイルのみ」と明示し、さらに `--allowedTools` でツールを限定することで、スコープ外への操作を二重に制限しています。Claudeが修復の範囲を広げようとしても、プロンプトの制約に引き戻されます。
ガードレール7:プッシュ先のオーナー確認
Gitのpush先リモートが自分のリポジトリかどうかを確認します。
ALLOWED_OWNERS="bokuwalily"
owner=$(git remote get-url origin | sed -E 's#.*github.com[:/]+([^/]+)/.*#\1#')GitHubのオーナー名が許可リストにない場合はプッシュを中止します。第三者のフォークリポジトリに誤ってプッシュしてしまうことを防ぐ安全装置です。また `origin` が設定されていないリポジトリの場合は、ローカルコミットだけ行ってプッシュはスキップします。
verify.shの設計指針
自己修復の品質は、verify.shの精度にほぼ依存します。verify.shが甘ければ壊れたままの状態が本番に反映されますし、厳しすぎると正常な修復も弾いてしまいます。
実際の動作を確認する
「コードファイルが存在する」「文法エラーがない」だけでは不十分です。実際に動かして期待する出力が返るかを確認します。たとえばデータ収集スクリプトであれば、テスト用の入力で1件以上のデータが取得できることを確認します。
副作用のないテストケースを使う
verify.shはlaunchdから自動で呼ばれます。実際の外部サービスに書き込んだり、本番データを変更したりすることは避け、読み取りのみまたはダミーデータで検証するよう設計します。
exit 0 / exit 1 を明確に返す
シェルスクリプトはexit 0(成功)とexit 1(失敗)で結果を伝えます。verify.shはこの慣例に従い、検証が通った場合のみexit 0を返すように書きます。self-repair.shはこの終了コードだけを見て判断します。戻り値が曖昧だと誤検知の原因になります。
再現手順:同じ仕組みを作るには
同じ仕組みを自分の環境で試したい場合の最小手順です。
ステップ1:ディレクトリを準備する
mkdir -p ~/.claude/self-repair/checks
mkdir -p ~/.claude/self-repair/state
mkdir -p ~/.claude/logsステップ2:registry.tsvを作る
# slug project_dir mode
my-automation ~/dev/my-project fullコード管理にGitを使っていない場合は mode を `detect` にしてください。
ステップ3:health.shとverify.shを作る
`~/.claude/self-repair/checks/my-automation-health.sh` に、スクリプトが正常に動いているかを確認するコマンドを書きます。問題なければexit 0を返します。
`~/.claude/self-repair/checks/my-automation-verify.sh` に、修復後の動作確認コマンドを書きます。同様にexit 0/1で結果を返します。両ファイルに実行権限を付与します。
chmod +x ~/.claude/self-repair/checks/my-automation-health.sh
chmod +x ~/.claude/self-repair/checks/my-automation-verify.shステップ4:plistを作成してlaunchdに登録する
`~/Library/LaunchAgents/com.yourname.self-repair.plist` を作成し、実行時刻と環境変数(PATHなど)を設定してから登録します。PATHにはclaudeコマンドが入っているディレクトリを必ず含めてください。
launchctl load ~/Library/LaunchAgents/com.yourname.self-repair.plistステップ5:ドライランで動作確認する
手動で実行して、検知のみ(実際の修復はしない)動作を確認します。
SELF_REPAIR_DRY=1 bash ~/.claude/scripts/self-repair.sh my-automation`SELF_REPAIR_DRY=1` を付けると、健全性チェックまでは行いますがClaudeを呼ばず、コードも変更しません。最初の確認に最適です。ログは `~/.claude/logs/self-repair.log` に書き出されます。
使い回すときの注意点
実際に組んでいて詰まった箇所と、汎用化する際の注意点をまとめます。
launchdのPATH問題
launchdから起動されたスクリプトは、ターミナルで普段使えるコマンドの多くが使えません。`node`・`python3`・`claude` など、`/opt/homebrew/bin` や `~/.local/bin` にあるコマンドは、plistの `EnvironmentVariables` に明示的にPATHを書かないと「コマンドが見つからない」エラーになります。実際のplistではPATHをフルパスで指定しています。
Gitリポジトリがないと自動修復は動かない
self-repair.shは git reset --hard で巻き戻す前提なので、Gitで管理されていないプロジェクトには自動修復を適用できません。registryのmodeを `detect` にして手動対応にしてください。スクリプトの中に `git rev-parse --git-dir` で事前確認が入っており、非Gitリポジトリでは安全にスキップします。
verify.shがなければ自動修復は実行されない
verify.shが存在しない(または実行権限がない)スラッグは、health.shが異常を返してもClaudeを呼ばずDiscordに「verify未整備のため自動修復せず」と通知します。面倒でも必ずverify.shを先に書いてください。
bash 3.2 互換を意識する
self-repair.shはmacOS標準の古いbash(バージョン3.2)でも動くよう書かれており、スクリプト冒頭のコメントにも「bash 3.2 互換(mapfile / 連想配列 禁止)」と明示されています。自前のhealth.shやverify.shを書くときも同様の制約を意識すると移植性が上がります。
通知先を先に用意する
修復成功・失敗の通知は外部の通知スクリプト経由でDiscordに飛ばす設定になっています。通知先が未設定でも処理自体は止まりません(fail-open設計)が、修復の成否を人間が把握できなくなります。運用前にDiscord Webhookか別の通知手段を必ず用意してください。
まとめ:AIの自己申告を信用しない設計の重要性
この仕組みで最も重要な考え方は、AIの自己申告を独立した検証で上書きするという点です。Claudeが「修復しました」と言っても、self-repair.sh自身がverify.shを実行して確認します。検証が通らなければ元の状態に戻します。
7つのガードレール(独立検証・git復元・secret走査・試行上限・コスト上限・スコープ制限・オーナー確認)は、「失敗しても安全に止まれる」という設計思想を具体化したものです。どれか1つが欠けると、連鎖的に問題が起きる可能性があります。
「壊れを検知して通知する」から「検知して修復を試み、失敗したら元に戻す」への一歩は、適切なガードレールがあれば十分に現実的です。完全に無人で動かすことにはそれなりの設計コストがかかりますが、registry.tsv・health.sh・verify.shの3ファイルを揃えて1つの自動化に適用するだけなら、今日から始められます。
📕 この記事の内容を、実装まで全部まとめた有料記事があります 手順・コード・テンプレをそのまま渡しています AIで「寝てても回る仕組み」をゼロから30日で組む全手順

コメント
ログイン か 会員登録 するとコメントできます