{"slug": "my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd", "title": "My Claude Code Logs Ballooned to 22MB — Nightly Auto-Trimming with launchd", "summary": "A developer built a log-rotation script for macOS launchd to prevent Claude agent logs from ballooning to 22MB. The script truncates files over 5MB to the last 2000 lines and deletes artifacts older than 30 days, keeping the log directory at around 20MB.", "body_md": "Nobody plans for log files. You wire up a background job, it works, and six weeks later a directory you never look at is quietly eating your disk.\n\n[In my previous post on automating self-audits](https://zenn.dev/bokuwalily/articles/cc-env-self-audit) I built the skeleton of a nightly job. This post is about the side effect of that: **the logs my resident agents keep spewing grew without bound**, and how I designed a trimming script wired into launchd to keep things quiet.\n\nOnce you have 20+ agents running as residents, logs pile up by hundreds of KB per day. Measured this morning (right after the 03:45 automated run), `~/.claude/logs/`\n\nwas **20M, and 21M as of now (10:43)**. 188 files. It was only a matter of time before it crossed 22MB.\n\nThe more launchd jobs there are, the more logs there are. Looking at the representative large files makes it obvious.\n\n```\n5.7M  agentmemory.err.log\n3.8M  vault-auto-ingest.log\n3.1M  hook-latency.jsonl\n2.7M  cost-log.jsonl\n 728K dotfiles-snapshot.log\n```\n\nEvery one of them **just streams errors and progress forever, and I almost never read the old lines**. All I need is \"what happened recently.\" And yet the logs keep accumulating, and the number from `du -sh ~/.claude/logs/`\n\njumps every day.\n\nPlain deletion is a problem — if the log is empty when I'm chasing down a recent error, I'm stuck. So instead of deleting, I went with **keeping only the tail**.\n\nThe policy of `log-rotate.sh`\n\nfits in three lines.\n\n`tail -n 2000`\n\nand overwriteI use the word \"rotate,\" but it isn't rotation. It just truncates the file. No backups either. The latest 2000 lines are plenty to trace a recent failure.\n\nHere is the full text of `~/.claude/scripts/log-rotate.sh`\n\n. It fits in 60 lines.\n\n``` bash\n#!/bin/bash\n# log-rotate.sh — ~/.claude/logs の肥大防止（毎日03:45 launchd）\n# 方針: 5MB超のログは末尾2000行だけ残して切詰め / 30日超の日付付き成果物・マーカー・bakは削除\nset -uo pipefail\nLOGDIR=\"$HOME/.claude/logs\"\nSELF_LOG=\"$LOGDIR/log-rotate.log\"\nMAX_BYTES=$((5 * 1024 * 1024))\nKEEP_LINES=2000\n\necho \"[$(date '+%F %T')] rotate start\" >> \"$SELF_LOG\"\n\n# 1) 肥大ログの切詰め（.log / .err / .out / .jsonl）\nfind \"$LOGDIR\" -maxdepth 1 -type f \\( -name '*.log' -o -name '*.err' -o -name '*.out' -o -name '*.jsonl' \\) | while read -r f; do\n  sz=$(stat -f%z \"$f\" 2>/dev/null || echo 0)\n  if [ \"$sz\" -gt \"$MAX_BYTES\" ]; then\n    tail -n \"$KEEP_LINES\" \"$f\" > \"$f.tmp\" && mv \"$f.tmp\" \"$f\"\n    echo \"[$(date '+%F %T')] truncated $(basename \"$f\") ($sz bytes -> $(stat -f%z \"$f\") bytes)\" >> \"$SELF_LOG\"\n  fi\ndone\n\n# 2) 日付付き成果物・マーカー・バックアップの30日超削除\nfind \"$LOGDIR\" -maxdepth 1 -type f \\( \\\n  -name 'daily-brief-2*.md' -o -name 'project-health-2*.md' -o \\\n  -name '.*-done-*' -o -name '.vault-ingest-*' -o -name '*.plist.bak*' \\\n  \\) -mtime +30 -delete 2>/dev/null\n\n# 3) 自分自身のログも肥大防止\nsz=$(stat -f%z \"$SELF_LOG\" 2>/dev/null || echo 0)\n[ \"$sz\" -gt \"$MAX_BYTES\" ] && tail -n \"$KEEP_LINES\" \"$SELF_LOG\" > \"$SELF_LOG.tmp\" && mv \"$SELF_LOG.tmp\" \"$SELF_LOG\"\n\necho \"[$(date '+%F %T')] rotate done (dir=$(du -sh \"$LOGDIR\" | cut -f1))\" >> \"$SELF_LOG\"\n```\n\nTruncation results are recorded in `log-rotate.log`\n\n. Here's actual output.\n\n``` php\n[2026-07-12 03:45:05] truncated agentmemory.err.log (5449070 bytes -> 331336 bytes)\n[2026-07-19 03:45:05] truncated hook-latency.jsonl (5376787 bytes -> 178061 bytes)\n[2026-07-19 03:45:05] truncated vault-auto-ingest.log (5255158 bytes -> 105277 bytes)\n[2026-07-26 03:45:05] rotate done (dir=20M)\n```\n\n`agentmemory.err.log`\n\nshrank from 5.2MB → 324KB, and `hook-latency.jsonl`\n\nfrom 5.1MB → 174KB.\n\nNote\n\nThe reason for the two-step`tail -n 2000 \"$f\" > \"$f.tmp\" && mv \"$f.tmp\" \"$f\"`\n\nis to prevent a redirect to the same file from blowing away its contents. Never use a direct`> \"$f\"`\n\noverwrite.\n\nHere is the full text of `~/Library/LaunchAgents/com.shun.log-rotate.plist`\n\n.\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n  \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>Label</key>\n    <string>com.shun.log-rotate</string>\n\n    <key>ProgramArguments</key>\n    <array>\n        <string>/bin/bash</string>\n        <string>~/.claude/scripts/log-rotate.sh</string>\n    </array>\n\n    <key>StartCalendarInterval</key>\n    <array>\n        <dict>\n            <key>Hour</key><integer>3</integer>\n            <key>Minute</key><integer>45</integer>\n        </dict>\n    </array>\n\n    <key>LowPriorityIO</key><true/>\n    <key>Nice</key><integer>10</integer>\n    <key>ProcessType</key><string>Background</string>\n\n    <key>StandardErrorPath</key>\n    <string>~/.claude/logs/log-rotate.err</string>\n</dict>\n</plist>\n```\n\nThree things matter here.\n\n| Key | Value | Purpose |\n|---|---|---|\n`LowPriorityIO` |\ntrue | Drops I/O to background priority so it doesn't block foreground work |\n`Nice` |\n10 | Lowers CPU priority (normally 0; larger values mean lower priority) |\n`ProcessType` |\nBackground | A declaration to the macOS scheduler, used in its thermal, battery, and memory pressure calculations |\n\nI set the start time to 03:45 to **avoid the cluster of other jobs around 03:30**. Autopilot runs at 05:00 and daily-brief at 06:00, so log cleanup gets done quietly before them.\n\nNote\n\nlaunchdskipsruns while the machine is asleep. If it was sleeping, the job won't run until the next matching time. If you want it to run reliably every morning, either enable wake on scheduler with`pmset -a wake 1`\n\n, or line up your non-sleeping hours with 03:45.\n\n`log-rotate.log`\n\nretains the size from each morning. Extracting July's progression gives this.\n\n```\n[2026-07-03] rotate done (dir=6.3M)\n[2026-07-06] rotate done (dir=9.5M)\n[2026-07-07] rotate done (dir=13M)\n[2026-07-11] rotate done (dir=17M)\n[2026-07-12] truncated agentmemory.err.log → rotate done (dir=13M)  ← 初回切詰め\n[2026-07-15] rotate done (dir=15M)\n[2026-07-19] truncated 2本  → rotate done (dir=8.1M)  ← 2回目\n[2026-07-25] rotate done (dir=18M)\n[2026-07-26] rotate done (dir=20M)\n```\n\nOn 07-12, when truncation ran, it dropped from 19M → 13M; on 07-19, from 17M → 8.1M. It keeps accumulating until a truncation target exceeds 5MB, then shrinks all at once the moment it does — a sawtooth pattern. The current 21M reflects SELF_LOG approaching 5MB, so it should shrink again at tomorrow's 03:45.\n\n`> \"$f\"`\n\n→ the file ends up empty`tail`\n\nreads it, leaving zero content. Always use the two-step tmp → mv.`StandardErrorPath`\n\n, stderr disappears`log-rotate.err`\n\n.`stat -f%z`\n\nis macOS-only`stat -c%s`\n\n. This plist is macOS launchd-specific so it's not an issue here, but be careful when taking the script to another environment.`find -mtime +30`\n\nis based on modification time`maxdepth 1`\n\npulls in files in subdirectories too`~/.claude/logs/`\n\nis flat right now, but that would cause accidents once I add subdirectories in the future. `-maxdepth 1`\n\nis mandatory.`Nice 10`\n\nalone still stalls when I/O gets heavy`LowPriorityIO`\n\n. Nice is CPU, LowPriorityIO is disk; they're separate axes.`~/.claude/logs/`\n\nballoons past 21M if left alone (188 files)`LowPriorityIO=true`\n\n+ `Nice=10`\n\n+ `ProcessType=Background`\n\nin launchd makes it run with low CPU and I/O load during the quiet hours after wakeNext time I plan to write about the other reason these logs pile up — **digging into the root cause of why agentmemory.err.log keeps hitting 5MB so frequently**.\n\n*Written by **Lily** — I ship iOS apps and automate my content stack with Claude Code.\n\nFollow along: [Portfolio](https://bokuwalily.com) · [X](https://x.com/bokuwalily) · [GitHub](https://github.com/bokuwalily)*", "url": "https://wpnews.pro/news/my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd", "canonical_source": "https://dev.to/bokuwalily/my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd-237l", "published_at": "2026-07-27 05:00:05+00:00", "updated_at": "2026-07-27 05:35:08.262632+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Claude"], "alternates": {"html": "https://wpnews.pro/news/my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd", "markdown": "https://wpnews.pro/news/my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd.md", "text": "https://wpnews.pro/news/my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd.txt", "jsonld": "https://wpnews.pro/news/my-claude-code-logs-ballooned-to-22mb-nightly-auto-trimming-with-launchd.jsonld"}}