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のログが22MBに膨れた ― launchdで夜間自動トリムする

0
Posted at

前回の「セルフ監査自動化」で夜間ジョブの骨格を組んだ。今回はその副作用 ―― 常駐エージェントが吐き続けるログが際限なく膨れた話と、切り詰めスクリプトを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行で要約できる。

  1. 5MB超 → tail -n 2000 して上書き(最新コンテキストを保持)
  2. 日付付き成果物・マーカー・bak → 30日超なら削除
  3. スクリプト自身のログも同じルールで整理

「回転」という言葉を使っているが、ローテーションではない。ファイルを切り詰めるだけだ。バックアップも作らない。最新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サービスを量産しています

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

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?