前回の「セルフ監査自動化」で夜間ジョブの骨格を組んだ。今回はその副作用 ―― 常駐エージェントが吐き続けるログが際限なく膨れた話と、切り詰めスクリプトをlaunchdに仕込んで静粛に抑える設計の記録だ。
エージェントを20本以上常駐させると、毎日数百KBずつログが積み上がる。計測した今朝(03:45の自動実行直後)の ~/.claude/logs/ は 20M、現時点(10:43)で21M。ファイル数は188本。放置すれば22MBを超えるのは時間の問題だった。
困りごと:毎朝起動のたびにディスクが重くなる
launchdジョブが増えるほどログも増える。代表的な大ファイルを見ると一目瞭然だ。
5.7M agentmemory.err.log
3.8M vault-auto-ingest.log
3.1M hook-latency.jsonl
2.7M cost-log.jsonl
728K dotfiles-snapshot.log
どれもエラーや進捗を垂れ流し続けるだけで、古い行を読むことはほぼない。必要なのは「最近何が起きたか」だけだ。それなのにログは累積し続け、du -sh ~/.claude/logs/ の数字が毎日跳ね上がる。
単純削除は困る ―― 直近のエラーを追いかける時に「ログが空」だと詰む。削除ではなく末尾だけ残す方針にした。
設計:削除しない・末尾2000行だけ残す
log-rotate.sh の方針は3行で要約できる。
-
5MB超 →
tail -n 2000して上書き(最新コンテキストを保持) - 日付付き成果物・マーカー・bak → 30日超なら削除
- スクリプト自身のログも同じルールで整理
「回転」という言葉を使っているが、ローテーションではない。ファイルを切り詰めるだけだ。バックアップも作らない。最新2000行あれば直近の障害を十分トレースできる。
log-rotate.sh の実装
~/.claude/scripts/log-rotate.sh の全文を引用する。60行に収まる。
#!/bin/bash
# log-rotate.sh — ~/.claude/logs の肥大防止(毎日03:45 launchd)
# 方針: 5MB超のログは末尾2000行だけ残して切詰め / 30日超の日付付き成果物・マーカー・bakは削除
set -uo pipefail
LOGDIR="$HOME/.claude/logs"
SELF_LOG="$LOGDIR/log-rotate.log"
MAX_BYTES=$((5 * 1024 * 1024))
KEEP_LINES=2000
echo "[$(date '+%F %T')] rotate start" >> "$SELF_LOG"
# 1) 肥大ログの切詰め(.log / .err / .out / .jsonl)
find "$LOGDIR" -maxdepth 1 -type f \( -name '*.log' -o -name '*.err' -o -name '*.out' -o -name '*.jsonl' \) | while read -r f; do
sz=$(stat -f%z "$f" 2>/dev/null || echo 0)
if [ "$sz" -gt "$MAX_BYTES" ]; then
tail -n "$KEEP_LINES" "$f" > "$f.tmp" && mv "$f.tmp" "$f"
echo "[$(date '+%F %T')] truncated $(basename "$f") ($sz bytes -> $(stat -f%z "$f") bytes)" >> "$SELF_LOG"
fi
done
# 2) 日付付き成果物・マーカー・バックアップの30日超削除
find "$LOGDIR" -maxdepth 1 -type f \( \
-name 'daily-brief-2*.md' -o -name 'project-health-2*.md' -o \
-name '.*-done-*' -o -name '.vault-ingest-*' -o -name '*.plist.bak*' \
\) -mtime +30 -delete 2>/dev/null
# 3) 自分自身のログも肥大防止
sz=$(stat -f%z "$SELF_LOG" 2>/dev/null || echo 0)
[ "$sz" -gt "$MAX_BYTES" ] && tail -n "$KEEP_LINES" "$SELF_LOG" > "$SELF_LOG.tmp" && mv "$SELF_LOG.tmp" "$SELF_LOG"
echo "[$(date '+%F %T')] rotate done (dir=$(du -sh "$LOGDIR" | cut -f1))" >> "$SELF_LOG"
切り詰め結果は log-rotate.log に記録される。実際の出力がこれだ。
[2026-07-12 03:45:05] truncated agentmemory.err.log (5449070 bytes -> 331336 bytes)
[2026-07-19 03:45:05] truncated hook-latency.jsonl (5376787 bytes -> 178061 bytes)
[2026-07-19 03:45:05] truncated vault-auto-ingest.log (5255158 bytes -> 105277 bytes)
[2026-07-26 03:45:05] rotate done (dir=20M)
agentmemory.err.log は5.2MB → 324KB、hook-latency.jsonl は5.1MB → 174KB まで縮んだ。
tail -n 2000 "$f" > "$f.tmp" && mv "$f.tmp" "$f" という2ステップにしているのは、同一ファイルへのリダイレクトが内容を吹き飛ばすのを防ぐため。> "$f" の直接上書きは絶対に使わない。
launchd の設定
~/Library/LaunchAgents/com.shun.log-rotate.plist の全文を引用する。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.shun.log-rotate</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>~/.claude/scripts/log-rotate.sh</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key><integer>3</integer>
<key>Minute</key><integer>45</integer>
</dict>
</array>
<key>LowPriorityIO</key><true/>
<key>Nice</key><integer>10</integer>
<key>ProcessType</key><string>Background</string>
<key>StandardErrorPath</key>
<string>~/.claude/logs/log-rotate.err</string>
</dict>
</plist>
3点が肝だ。
| キー | 値 | 狙い |
|---|---|---|
LowPriorityIO |
true | I/Oをバックグラウンド優先度に落とし、フォアグラウンド作業をブロックしない |
Nice |
10 | CPU優先度を下げる(通常は0、値が大きいほど低優先) |
ProcessType |
Background | macOSスケジューラへの宣言。熱・バッテリー・メモリ圧力の計算に使われる |
起動時刻を03:45にした理由は03:30前後に他のジョブが集中するのを避けるため。autopilotが05:00、daily-briefが06:00と並んでいるので、ログ整理はその前に静かに済ませる。
launchdはスリープ中の起動をスキップする。スリープに入っていた場合、次の該当時刻まで実行されない。毎朝確実に実行させたいなら pmset -a wake 1 でwake on schedulerを有効にするか、スリープしない時間帯を03:45前後に合わせる。
ディレクトリサイズの推移
log-rotate.log に毎朝のサイズが残っている。7月の推移を抜き出すとこうなった。
[2026-07-03] rotate done (dir=6.3M)
[2026-07-06] rotate done (dir=9.5M)
[2026-07-07] rotate done (dir=13M)
[2026-07-11] rotate done (dir=17M)
[2026-07-12] truncated agentmemory.err.log → rotate done (dir=13M) ← 初回切詰め
[2026-07-15] rotate done (dir=15M)
[2026-07-19] truncated 2本 → rotate done (dir=8.1M) ← 2回目
[2026-07-25] rotate done (dir=18M)
[2026-07-26] rotate done (dir=20M)
切り詰めが走った07-12は19M → 13M、07-19は17M → 8.1Mに落ちた。切り詰め対象が5MBを超えるまでは溜まり続け、超えた瞬間に一気に縮む ―― というのこぎり波パターンだ。現時点の21MはSELF_LOGが5MBに近づいており、明日の03:45で再び縮む見込みだ。
踏んだ落とし穴
-
> "$f"で直接上書き → ファイルが空になる ―― シェルはリダイレクトを先に処理する。tailが読む前にファイルが切り詰められ、中身がゼロになる。必ず tmp → mv の2ステップ。 -
launchd の
StandardErrorPathを指定しないと stderr が消える ―― スクリプトのエラーが無音で捨てられる。log-rotate.errを別ファイルで受ける。 -
stat -f%zは macOS 専用 ―― Linux ではstat -c%s。この plist は macOS launchd 専用なので問題ないが、スクリプトを他環境に持ち出す時は注意。 -
find -mtime +30の基準は変更時刻 ―― 作成から30日ではなく、最終変更から30日。更新が続くファイルは消えない(意図通り)。 -
maxdepth 1を外すとサブディレクトリのファイルも対象になる ――~/.claude/logs/は現状フラットだが、将来サブディレクトリを切ったとき事故が起きる。-maxdepth 1は必須。 -
Nice 10 だけでは I/O が重い局面で詰まる ――
LowPriorityIOも必ず組み合わせる。Nice はCPU、LowPriorityIO はディスクで別の軸。
まとめ
- 自動化エージェントを20本以上常駐させると
~/.claude/logs/は放置で21M超まで膨れる(188ファイル) - 「削除」ではなく「5MB超 → 末尾2000行だけ残してtruncate」にすることで最新コンテキストを保持したまま肥大を抑える
- launchdに
LowPriorityIO=true+Nice=10+ProcessType=Backgroundを組み合わせると、スリープ明けの静粛時間帯にCPU・I/Oともに低負荷で実行される - 切り詰めは のこぎり波パターン ―― 5MBを超えた瞬間に一気に縮み、また溜まっていく。2000行のウインドウが最近の障害トレースには十分な深さだった
次回は、このログが溜まるもう一方の理由 ―― agentmemory.err.log が高頻度で5MBに達し続ける根本原因を掘った話を書く予定です。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
- 制作物・記事は bokuwalily.com にまとめています🖥️
- AIで「寝てても回る仕組み」を作って月120万にした話は noteの有料記事 に💰
- OSS: github.com/bokuwalily 🐙
- 最新情報・お問い合わせは X @bokuwalily へ🌍
皆さんの ❤️ やシェアが励みになります!