✂️ Claude Codeのログが静かに膨らむ問題を毎晩tail切り詰めで自己解決する — リーダー×
✂️

Claude Codeのログが静かに膨らむ問題を毎晩tail切り詰めで自己解決する

#claudecode#launchd#bash#自動化2026-08-03 · 約7

「Claude Code環境」シリーズです。前作「エージェントログをtrimする」でログ肥大に触れましたが、今回はその具体的な実装 ―― launchdで毎晩3:45に回るtail切り詰めスクリプトの話です。

~/.claude/logs/ は放っておくと静かに膨らみます。直近では agentmemory.err.log が1週間で5.3MBに達し、その前週は hook-latency.jsonlvault-auto-ingest.log が同時に5MB超えで合計ディレクトリが20Mに届きました。監視スクリプトが tail -f でファイルを掴んでいるので、ファイルを新規作成するlogrotate方式は使えません。ファイルパスを変えずに中身だけ削る方式を選んだ理由がここにあります。

困りごと:logrotateはパスを変える

logrotate の標準動作は「古いファイルを .1 にリネームして新ファイルを作る」です。ファイルディスクリプタをinode経由で掴んでいる tail -finotifywait は追跡し続けますが、パスで読んでいるスクリプト(Claude Codeのステータスライン・cron・autopilot)は新ファイルを見に行かず、ログが空に見える状態になります。

毎回ローテーション後にスクリプト側のパス設定を書き換えるのは運用として続かない。だから「パスは固定、末尾だけ残す」方針にしました。

解決策:tail で末尾だけ残して mv 上書き

~/.claude/scripts/log-rotate.sh の核心がこの3行です。

MAX_BYTES=$((5 * 1024 * 1024))  # 5MB超えたら切詰め対象
KEEP_LINES=2000

tail -n "$KEEP_LINES" "$f" > "$f.tmp" && mv "$f.tmp" "$f"
  1. stat でバイト数を計測して5MB超のファイルだけを対象にする
  2. tail -n 2000 で末尾2000行をテンポラリに書き出す
  3. mv でテンポラリを元ファイルに上書き → inodeは変わらず、パスも変わらず

> "$f" の直接リダイレクトではなく > "$f.tmp" && mv にしているのは、書き込み中のクラッシュでファイルが空になるのを防ぐためです。

logrotate -ccopytruncate オプションでも「コピー後に元ファイルをtruncate」できますが、コピーと切り詰めの間にセッションが大量書き込みをすると最大で数秒分のログが消えます。tail + mv はその窓がない分、こちらのほうが安全です。

スクリプト全文

#!/bin/bash
# log-rotate.sh — ~/.claude/logs の肥大防止(毎日03:45 launchd)
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"

-maxdepth 1 でサブディレクトリには降りず、~/.claude/logs/ 直下だけを対象にしています。サブディレクトリには別用途のプロジェクトデータが入っていることがあるため、誤削除を防ぐ保険です。

launchd で深夜静音実行

~/Library/LaunchAgents/com.shun.log-rotate.plist の該当部分です。

<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>
<key>StartCalendarInterval</key>
<array>
  <dict>
    <key>Hour</key>
    <integer>3</integer>
    <key>Minute</key>
    <integer>45</integer>
  </dict>
</array>

3つの設定が「深夜静音実行」を作っています。

キー効果
LowPriorityIOtrueディスクI/Oの優先度を下げ、他プロセスに割り込まない
Nice10CPU優先度を下げる(0が標準、正値ほど低優先)
ProcessTypeBackgroundmacOSがバックグラウンド品質クラスで管理する

StartCalendarInterval を3:45にしているのは、autopilotが5:00に起動するため、その直前にロールアップを終わらせておく意図です。Macがスリープ中のジョブはスキップされるため、深夜に画面を閉じているとその日は走りません。これは割り切っています。

pmset -g sched でスリープ解除スケジュールを組めますが、毎朝起こすと充電中でもバッテリーに悪影響が出ます。スリープ時スキップを許容してバッテリーを守る方を選びました。

30日超の dated artifact も削除する

ジョブは切り詰めと並行して、-mtime +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

daily-brief-2*.mdproject-health-2*.md はClaude Codeの自動タスクが日次で吐く成果物です。1ファイル数KB〜数十KBでも、毎日積めば年間で数GBになります。30日を過ぎたら用済みとして消します。.*-done-*.vault-ingest-* はcronジョブのチェックポイントファイルで、これも放置すると何百個にもなります。

実際の切り詰め記録

~/.claude/logs/log-rotate.log から直近の実績です。

[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-19 03:45:05] rotate done (dir=8.1M)

[2026-07-27 03:45:05] truncated agentmemory.err.log (5590723 bytes -> 297105 bytes)
[2026-07-27 03:45:06] rotate done (dir= 16M)

hook-latency.jsonl が5.1MB → 174KBに、vault-auto-ingest.log が5.0MB → 103KBに。2ファイルだけでその日のディレクトリが17M → 8.1Mに半減しています。直近2026-07-27の実行では agentmemory.err.log が5.3MBに達していたところを290KBまで落としました。

踏んだ落とし穴

  • > "$f" の直接リダイレクトで書き込み途中にクラッシュtmp 経由の mv に変えて原子的に
  • find.claude/ 配下を再帰してプロジェクトデータを削除しかけた-maxdepth 1 で直下のみに限定
  • launchd の最小 PATH でスクリプトが stat を見つけられなかった → plist の EnvironmentVariables/opt/homebrew/bin を明示
  • Macスリープ中にジョブがスキップされてログが何日も放置 → スキップを許容し、起きたタイミングで手動 launchctl kickstart を1コマンドにしておく
  • 自分自身の log-rotate.log も肥大することに気づいた → スクリプト末尾でセルフ切り詰めを追加(スクリプト内に3セクション目として組み込み済み)

まとめ

  • ~/.claude/logs/.log / .err / .out / .jsonl が積み上がり、放置すると20M超になる
  • logrotate の新ファイル作成ではなく tail -n 2000 > tmp && mv tmp $f で上書き → ファイルパスが変わらず監視スクリプトが壊れない
  • launchdの LowPriorityIO + Nice=10 + ProcessType=Background で深夜3:45に静かに走らせる
  • 30日超の daily-brief-*.md 等の dated artifact も同じジョブで find -mtime +30 -delete して片付ける

次回は、このログに記録が溜まったセッション履歴をどう長期記憶として再利用しているか ―― 会話ログを長期記憶に変える話 につながります。


Lily@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています

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