14 Pitfalls of Letting a Claude Code Environment Rot — and the 70-Line Weekly Audit That Catches Them A developer built a 70-line script and launchd job that snapshots a Claude Code environment every Sunday to catch configuration rot before it degrades performance. The audit parses settings, plugins, MCP servers, hooks, and auto-skills, writing date-stamped reports that allow diffing to pinpoint when a connection or plugin broke. The developer warns that unused MCP servers and plugins accumulate silently, taxing the context window and slowing startup, and that automation is necessary because users' tolerance for sluggishness creeps up over time. Nobody notices their dev environment rotting. It happens one broken MCP server at a time, and by the time you feel it, you have no idea which week it started. My fix was to stop trying to feel it and start measuring it: a 70-line script and a launchd job that snapshot the whole thing every Sunday morning. The more you use Claude Code, the more files pile up. An MCP server you tried once, a plugin you added because it "looked useful," an auto-skill you wrote on impulse — each had a purpose the moment you added it. The problem is what happens after. An MCP server whose auth token has expired will sit in your config as Failed to connect forever. A plugin's command files can still be on disk while it's gone from enabledPlugins in settings.json — dead weight taking up space. An auto-skill you wrote as a "good enough for now" procedure is still sitting in ~/.claude/skills/auto/ six months later, and you keep paying the context tax of Claude loading it every single time. This is nothing like "your iPhone doesn't get slower just because you have unused apps." Claude Code's context window is finite, and the total volume of settings, skills, and hooks loaded at startup directly affects the quality of that first response. If 50 plugins are enabled, their metadata rides along in context every time. If 10 MCP servers are stuck at Failed , connection-attempt timeouts drag out your startup. I learned this the hard way three months into using Claude Code seriously. As my revenue grew, I kept adding MCPs to make things "even more convenient" — and then one week Claude's first response was noticeably sluggish. I dug in and found seven Failed to connect lines in the output of claude mcp list . Five of them I had no memory of ever installing — they'd been added automatically via plugins. The most expensive state to be in is not knowing something is broken . Three weeks of running with a broken connection means cumulative minutes upon minutes of timeout waiting. A problem you could fix in 10 seconds if you noticed it is pure loss when you don't. So I built a weekly "health check" that runs automatically and accumulates reports in date-stamped files. Take a diff and you can trace back to exactly which week your MCPs started breaking. It converts the vague feeling of "things seem slow lately" into the fact that " Failed went to 3 as of the report from three Sundays ago." There's one more reason this works especially well: rot you can't perceive yourself can only be detected by automation. When you use Claude Code every day, your threshold for "feels heavy" keeps creeping up. It can be 20% slower than three months ago and that just becomes normal. Without weekly snapshots, you lose the baseline for comparison. The whole thing is just two files: the diagnostic script, and a launchd job config that fires it weekly. 毎週日曜 09:00 │ ▼ launchd が com.shun.env-audit を起動 │ ▼ ~/.claude/scripts/env-audit.sh を実行 │ ├─ jq で settings.json をパース │ └─ enabledPlugins の数を取得 │ ├─ find で plugin ディレクトリを走査 │ └─ commands / SKILL.md / agents の実ファイル数 │ ├─ claude mcp list(timeout 25 秒) │ └─ Connected / Needs auth / Failed を集計 │ ├─ jq で hooks の構成を出力 │ ├─ ls ~/.claude/skills/auto/ で auto-skill 一覧 │ └─ ccusage blocks --active で直近コストを取得 │ ▼ ~/.claude/logs/env-audit-YYYYMMDD.md に書き出し │ ▼ diff で前週比較 → 「いつ壊れたか」を遡れる ~/.claude/scripts/env-audit.sh is 70 lines. The whole thing is a single {} block that generates Markdown, redirected to a file. bash /usr/bin/env bash set -uo pipefail OUT="${1:-/tmp/claude-env-audit.md}" SETTINGS="$HOME/.claude/settings.json" { ... Markdown を echo で生成 ... } "$OUT" With no argument it writes to /tmp/claude-env-audit.md . When launchd calls it, it passes a date-stamped path as the argument more on that below . The Plugin Inventory section counts both the config file and the actual files on disk. TOTAL=$ jq -r '.enabledPlugins // {} | length' "$SETTINGS" echo "- Enabled plugins: $TOTAL " echo "- Plugin commands on disk: $ find $HOME/.claude/plugins -path ' /commands/ .md' 2 /dev/null | wc -l | tr -d ' ' " echo "- Plugin skills on disk: $ find $HOME/.claude/plugins -name 'SKILL.md' 2 /dev/null | wc -l | tr -d ' ' " echo "- Plugin agents on disk: $ find $HOME/.claude/plugins -path ' /agents/ .md' 2 /dev/null | wc -l | tr -d ' ' " The gap between the enabledPlugins count and the number of real files on disk is your "zombie file" indicator. Plugins that have been removed from the config but remain on disk don't cost you context, but they're cleanup candidates. Conversely, if something is listed in enabledPlugins but has no files on disk, that plugin isn't working. The MCP Server Status section is the heart of it. MCP OUT=$ timeout 25 claude mcp list 2 &1 TOTAL MCP=$ printf '%s' "$MCP OUT" | grep -cE "://|^plugin:|^claude\.ai" OK=$ printf '%s' "$MCP OUT" | grep -c "Connected" AUTH=$ printf '%s' "$MCP OUT" | grep -c "Needs auth" FAIL=$ printf '%s' "$MCP OUT" | grep -c "Failed to connect" timeout 25 matters. When an MCP server is unresponsive, claude mcp list itself can hang. Putting a 25-second timeout on it means a broken server won't stall the entire script. The results are output both as a one-line summary and as detailed lists of Failed / Needs auth . echo "- Total: $TOTAL MCP / Connected: $OK / Need auth: $AUTH / Failed: $FAIL " Next, the Hooks section uses jq to list event types and registration counts. jq '.hooks | to entries | map {event: .key, count: .value | length } ' "$SETTINGS" 2 /dev/null Hooks are a category that grows easily and feels scary to delete from. Checking "how many hooks are attached to UserPromptSubmit" weekly lets you catch unintentionally duplicated hooks piling up early. The Auto-skills section simply prints a list. ls "$HOME/.claude/skills/auto/" 2 /dev/null | grep -v README In my environment there are currently more than 20 SKILL.md files in ~/.claude/skills/auto/ . Keeping the ones whose purpose has expired inflates the context Claude loads in its system prompt. Eyeballing the list weekly gives you the trigger for "oh right, I don't use this anymore." The Cost section pulls the cost of the most recent active block. ccusage blocks --active 2 &1 | grep -E "Block|Time|Tokens:|Cost:|/h" | sed 's/^/ /' In weeks where MCP connection failures increase, retry costs can get tacked on. Putting MCP status and cost trends in the same report lets you read the correlation after the fact: "the reason cost spiked this week was MCP instability." The Recommendations section is threshold-based automatic judgment. "$FAIL" -gt 0 && echo "- ⚠️ $FAIL MCP servers failed. Review/disable to reduce startup time." "$AUTH" -gt 5 && echo "- ⚠️ $AUTH MCP servers unauthenticated. Either auth or disable to reduce noise." "$TOTAL" -gt 50 && echo "- ⚠️ $TOTAL plugins enabled - likely heavy context tax. Consider pruning unused." Rationale for the thresholds: FAIL 0 is zero tolerance even one failure needs handling . AUTH 5 comes from experience — "up to 5 is an acceptable range where per-project auth prompts can legitimately be pending." plugins 50 I set from the experience that "past 50, the amount of context injected at startup gets perceptibly heavy." ~/Library/LaunchAgents/com.shun.env-audit.plist is the weekly trigger.