この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます

― コストで暴走を止めるautopilot
この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます
「Claude Code環境」シリーズの第8弾です。会話ログを長期記憶に変える話で無人ジョブを紹介しましたが、今回はその親玉 ―― ユーザー入力ゼロでClaude Codeに自分の環境を改善させ続けるautopilotの話です。
「自律ループ」と言うと聞こえはいいですが、無人でLLMを回すと2つこわいことが起きます。①プラン枠を食い潰す と ②同じタスクを延々と繰り返す。実際どちらも踏みました。この記事は、その2つをどう機械で止めたかの記録です。
launchdから毎朝5:00に起動して、`claude -p` のヘッドレスで「改善タスクを1つ拾って、1 Phaseだけ進めて終わる」を繰り返します。
無人で「賢いことを少しずつ」やらせるのが狙いで、1回で大改造はさせません。
最初のゲートはコストです。5時間ブロックの出力トークンを見て、危険水位なら走る前に止めます。
BUDGET=$(~/.claude/scripts/token-budget-advisor.sh --short)
if echo "$BUDGET" | grep -qE '🔴|critical|cap-near'; then
log "ABORT: budget critical"; exit 0
fiただラベル判定だけだと穴がありました。`🟡burst` 表示のまま実際の残量がマイナス、というケースが素通りして、残量−8003で重いモデルが606秒走ったことがあります。なのでラベルとは別に、残量を数値で再計算して0以下なら実行しません。
REMAINING=$((800000 - BLOCK_OUT)) # 5h block cap 800k 想定
if [ "$REMAINING" -le 0 ]; then
log "SKIP: block exhausted"; exit 0
fi:コスト上限は「ラベル」ではなく「残量の数値」で持つべきでした。色や文字列のしきい値判定は、境界で必ずすり抜けます。最後は引き算で `<= 0` を見るのが確実です。
さらに残量に応じて effort と max-turns を可変にします。残りが少なければ軽量・少ターン、余裕があれば高めに。無人ジョブの既定モデルは安価なものにしています(高性能モデルの常用で5hブロックを食い潰し、以降の全スロットがSKIPになった事故の反省)。重いタスクを意図的に回すときだけ環境変数で上書きします。
MODEL="${AUTOPILOT_MODEL:-claude-sonnet-4-6}" # 既定は安価モデルターン数だけでは止まらないので、壁時計のtimeoutも被せます。max-turnsはターン数しか縛らず、7.25時間走った実績があったため、`gtimeout 7200`(2h)で頭打ちにします。
これがいちばん効いた修正です。タスク候補を `next-session-todo.md` の「High impact」セクションから拾うのですが、最初のawkが壊れていて候補が常に空でした。
# 旧実装の範囲パターン /^### High impact/,/^###/ は
# 開始行自身が終端条件にも一致して即終了 → 常に空。
# → フォールバックの固定タスクが毎回選ばれ、同一タスクを6日で17回反復。候補が空だとフォールバックの固定タスクが毎回選ばれ、同じタスクを6日間で17回やっていました。範囲パターンをフラグ方式に直して候補を正しく列挙し、さらに履歴ベースのdedupeを足しました。
if any(r.get("exit_code") == 0 for r in recent):
print("done-recently") # 最近やった → 次の候補へ
if len(recent) >= 2 and all(r["exit_code"] != 0 for r in recent[-2:]):
print("failing-repeatedly") # 連続失敗 → 諦めて次へ履歴は1行1レコードのJSONLで持ち、タスク名・exit code・所要秒・モデル・effortを記録します。これで「最近やった/詰まり続けている」を機械で判定できます。
無人で回すと、`claude -p` の「N MB回収しました」という自己申告を誰も検証しない問題が出ます。そこでharness側で実測を取り、結果ファイルに並記します。
DISK_BEFORE_KB=$(du -sk "$HOME/.claude" | awk '{print $1}')
# ...claude -p 実行...
DISK_AFTER_KB=$(du -sk "$HOME/.claude" | awk '{print $1}')
DISK_DELTA_KB=$(( DISK_AFTER_KB - DISK_BEFORE_KB ))
FILES_TOUCHED=$(find "$HOME/.claude" ... -newer "$RUNSTART_REF" | wc -l)結果ファイルの末尾に「## 自己検証(harness実測 / claudeの主張ではない)」として disk delta と変更ファイル数を書き、「本文の数値主張がこの実測と乖離する場合は本文を疑う」と添えます。AIに語らせた数字と、OSが計った数字を必ず並べる。これが無人運用の信頼性の肝でした。
暴走を止めるには、人間側もコストを常時見たい。Claude Codeのステータスラインに、5h/7dのプラン使用率を出しています。嬉しいのは、これがstdinから直接取れること。認証もエンドポイント呼び出しも不要です。
H5=$(j '.rate_limits.five_hour.used_percentage')
H5R=$(j '.rate_limits.five_hour.resets_at')
D7=$(j '.rate_limits.seven_day.used_percentage')
CTX=$(j '.context_window.used_percentage')`rate_limits` は「サブスクで最初のAPI応答後にだけ現れる」ので、初回ターンまでは `--` にフォールバックします。これで「🕐 5h 42% ⏪14:30 / 📅 7d 18%」のように、プラン枠とリセット時刻が常に見えます。autopilotが裏で食った分も、ここに即反映されます。
ここまでの8本で、記憶・スキル・コンテキスト・launchd・協業・セキュリティ・長期記憶・自律ループと、私のClaude Code環境のほぼ全体を書きました。読んでくれてありがとうございました。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
◼︎作ったアプリは **ポートフォリオ** にまとめています📱
◼︎新着・開発の裏側は X **@bokuwalily** で発信しています🐦
◼︎OSS: **github.com/bokuwalily**🐙
皆さんの ❤️ やシェアが励みになります!