「Claude Code環境」シリーズの続きです。前作のgit設定バックアップ自動化 に続いて、今回は 有効にしたまま放置した MCP プラグインがコンテキストを静かに削り続ける問題 と、それを週次で自動 disable する3段パイプラインを書きます。
プラグインを enable すると、一度も呼ばれなくても起動時に tool schema が注入されます。「入れっぱなし」が積み重なると Claude が実際に使えるトークン量が静かに削れていく。この記事ではその検出から自動 disable・キャッシュアーカイブまで実コードで一本につなぎます。
困りごと:足すが引かないプラグイン管理
新しいプラグインを入れる動機は明確です。「このツールが使いたい」という能動的な衝動があります。外す動機は存在しません。「あのプラグイン最近使ってないな」と気づく機会がそもそもない。
Claude Code はセッション開始時、settings.json の enabledPlugins で true になっているプラグイン全件の tool schema を context に注入します。「有効だけど30日一度も呼ばれていない」プラグインが増えるほど、毎回のコンテキスト先頭がゼロ貢献で埋まります。
数ヶ月で試して enable したまま忘れたプラグインが積み重なると Dormant(有効なのに未使用)が30本を超えることがあります。
構成:3段パイプライン
plugin-usage.sh → 休眠プラグインを可視化(手動実行 / 週次レポート)
plugin-auto-disable.sh → 30日未使用を週次で自動 disable
cleanup-plugin-cache.sh → 無効化済みキャッシュを .disabled-cache/ へアーカイブ
plugin-auto-disable.sh apply の末尾が cleanup-plugin-cache.sh apply を連鎖呼び出しする構成なので、launchd から起動するエントリポイントは1本です。
Step 1 — plugin-usage.sh で休眠プラグインを可視化
~/.claude/scripts/plugin-usage.sh は過去 N 日(デフォルト14日)のセッション JSONL から、実際に tool_use として呼ばれたプラグインだけを集計します。
# plugin-usage.sh 抜粋
LOG_DIR="$HOME/.claude/projects/$(echo "$HOME" | tr '/' '-')"
DAYS="${1:-14}"
find "$LOG_DIR" -maxdepth 1 -name "*.jsonl" -mtime -"$DAYS" -print0 2>/dev/null | xargs -0 cat 2>/dev/null \
| jq -R -r 'fromjson? | select(.type=="assistant") | .message.content[]? | select(.type=="tool_use")
| if .name=="Skill" then ((.input.skill // "") | select(contains(":")) | split(":")[0])
else (.name | select(startswith("mcp__plugin_")) | sub("^mcp__plugin_";"") | split("_")[0]) end' 2>/dev/null \
| sort | uniq -c | sort -rn > "$TMP"
enabled plugin 数と distinct 呼び出し数の差を Dormant として出します。
TOTAL_ENABLED=$(jq -r '[.enabledPlugins // {} | to_entries[] | select(.value)] | length' "$SETTINGS")
# Dormant = TOTAL_ENABLED - USED_COUNT
なぜ grep ではなく jq で集計するか
旧実装は grep で部分文字列マッチしていました。JSONL 内の「deferred tools 全列挙」行(使用可能ツール名の長大なリスト)を「呼び出し」として誤計上し、terraform 等が呼んでいないのに上位に現れたり、Dormant 数がマイナスになったりしていました。select(.type=="tool_use") を通すことで 実際に呼ばれた呼び出しだけ を数えるよう直しています。
出力イメージ:
## Summary
- Enabled plugins: **62**
- Distinct plugins referenced in logs: **29**
- Dormant (enabled but never invoked): **33**
## Dormant Plugins (enabled, no recent invocation)
_Candidates for disabling to reduce context tax_
...
## Recommendation
- 33 plugins enabled but unused in last 14 days
- Heavy dormant load — disabling these could free significant context budget
- To disable a plugin: edit `enabledPlugins` in ~/.claude/settings.json
Dormant が30超になると "Heavy dormant load" と出ます。このしきい値はスクリプト内にハードコードされています。
Step 2 — plugin-auto-disable.sh が週次で自動 disable
~/.claude/scripts/plugin-auto-disable.sh が本体です。デフォルトは dry(変更なし)、apply を渡すと実際に無効化します。
# plugin-auto-disable.sh 抜粋
MODE="${1:-dry}"
DAYS="${DAYS:-30}" # 観測ウィンドウ(デフォルト30日)
MIN_CACHE_MB="${MIN_CACHE_MB:-5}" # 5MB未満のキャッシュは対象外
WEEKLY_MAX="${WEEKLY_MAX:-5}" # 1回の実行で最大5件
動作フロー:
- 過去30日の JSONL から MCP 呼び出し+Skill 呼び出しをインデックス化
-
enabledPluginsでtrueのプラグイン一覧を python3 で取得 - PROTECTED リストに含まれるものを除外
- 候補をキャッシュサイズ降順でソートし、
MIN_CACHE_MB以上のものをWEEKLY_MAX件まで絞る -
applyモード時はplugin-disable.sh apply→cleanup-plugin-cache.sh applyを順に実行
# 候補をサイズでソートして WEEKLY_MAX 件選出
SIZED=()
for p in "${CANDIDATES[@]}"; do
size=$(du -sm "$HOME/.claude/plugins/cache/claude-plugins-official/$p" 2>/dev/null | awk '{print $1}')
size="${size:-0}"
[ "$size" -lt "$MIN_CACHE_MB" ] && continue
SIZED+=("${size}\t${p}")
done
SELECTED=()
while IFS=$'\t' read -r sz p; do
SELECTED+=("$p")
[ "${#SELECTED[@]}" -ge "$WEEKLY_MAX" ] && break
done < <(printf '%b\n' "${SIZED[@]}" | sort -rn)
キャッシュサイズ降順で優先するのは削れるコンテキスト量が大きいものから処理するためです。
PROTECTED リストの設計
自動 disable の最大のリスクは誤検出です。「ほぼ使わないけど消えると困る」プラグインを守るために PROTECTED 配列があります。
PROTECTED=(
remember plugin-dev hookify skill-creator session-report
security-guidance superpowers context7 explanatory-output-style
learning-output-style code-review feature-dev claude-md-management
# LSPs: Claude Code が透過的に呼び出す可能性。tool_use では現れない
typescript-lsp pyright-lsp php-lsp ruby-lsp rust-analyzer-lsp swift-lsp
# Process tools: ad-hoc に呼ばれる可能性
code-simplifier code-modernization ralph-loop agent-sdk-dev mcp-server-dev
playground commit-commands pr-review-toolkit
# 既知の誤検出(過去のセッションでトラブル)
azure-cosmos-db-assistant
)
カテゴリごとの保護理由:
| カテゴリ | 代表 | 守る理由 |
|---|---|---|
| インフラ系 |
hookify superpowers remember
|
起動時フック・スキル呼び出しの核。JSONL に tool_use として現れない |
| LSP 系 |
typescript-lsp 等 |
Claude Code 本体が内部で透過的に呼ぶ。ログに記録されない |
| 開発支援 |
code-review feature-dev
|
ad-hoc に使う。呼ばない期間が長くても必要 |
| 既知誤検出 | azure-cosmos-db-assistant |
過去に自動 disable して困った実績あり |
LSP 系を PROTECTED に入れないと即詰まる
typescript-lsp 等は Claude Code 本体が内部で使います。ユーザーが明示的に Skill ツールや MCP ツールとして呼ぶわけではないため tool_use ログに現れません。PROTECTED に入れていないと、普通に使っているのに自動 disable されます。
Step 3 — cleanup-plugin-cache.sh でキャッシュをアーカイブ
disable しても ~/.claude/plugins/cache/ にキャッシュは残ります。cleanup-plugin-cache.sh がこれを ~/.claude/plugins/.disabled-cache/ へ移動します。
# cleanup-plugin-cache.sh 抜粋
CACHE_DIR="$HOME/.claude/plugins/cache/claude-plugins-official"
ARCHIVE_DIR="$HOME/.claude/plugins/.disabled-cache"
PURGE_DAYS="${PURGE_DAYS:-30}"
for dir in "$CACHE_DIR"/*/; do
name=$(basename "$dir")
if ! echo "$ENABLED" | grep -qx "$name"; then
if [ "$MODE" = "apply" ]; then
mv "$dir" "$ARCHIVE_DIR/" && echo " archived: $name"
fi
fi
done
実削除はしません。mv で退避するだけなので、復元は mv 一発で可逆です。
さらに purge サブモードで .disabled-cache/ に PURGE_DAYS(デフォルト30日)以上放置されたものだけを物理削除できます。
# アーカイブから30日後に物理削除
~/.claude/scripts/cleanup-plugin-cache.sh purge
launchd で日曜06:45に自動化
~/Library/LaunchAgents/com.shun.plugin-auto-disable.plist で週次スケジュールを設定しています。
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>45</integer>
<key>Weekday</key>
<integer>0</integer> <!-- 0 = 日曜 -->
</dict>
毎週日曜の06:45に plugin-auto-disable.sh apply が走り、ログは ~/.claude/logs/plugin-auto-disable.log に追記されます。
登録と確認:
# 登録
launchctl load ~/Library/LaunchAgents/com.shun.plugin-auto-disable.plist
# 稼働確認
launchctl list | grep plugin-auto-disable
# 手動テスト(変更なし)
~/.claude/scripts/plugin-auto-disable.sh dry
# 手動実行
~/.claude/scripts/plugin-auto-disable.sh apply
踏んだ落とし穴
-
grep の部分マッチが deferred tools 列挙行を「呼び出し」として拾う → jq で
type=="tool_use"を明示するまで Dormant 数がマイナスになっていた。terraform 等が幽霊で上位に現れていた -
LSP 系を PROTECTED に入れ忘れて disable される →
typescript-lspが消えてから型チェックが効かなくなった。PROTECTED に追加して復元 -
MIN_CACHE_MBを設定しないと微小キャッシュのプラグインが対象になる → 5MB 未満は impact が小さくリスクだけある。デフォルト5MBでフィルタ -
WEEKLY_MAXを大きくしすぎると必要プラグインも巻き込む → 週5件上限。急いで一気にやる必要はない - launchd が Mac スリープ中にジョブをスキップする → 日曜朝06:45 は起動中が多い時刻。スキップされても翌週に持ち越されるだけで実害なし
-
plugin-disable.shが存在しないと apply が空振り →plugin-auto-disable.shは内部でこのスクリプトを呼ぶラッパー構成。スクリプト一式を揃えないと動かない
まとめ
-
plugin-usage.shでセッション JSONL の実tool_useを集計し、Dormant プラグインを可視化する - 旧 grep 実装は deferred tools 列挙行を誤計上する。jq で
type=="tool_use"を明示するのが正解 -
plugin-auto-disable.shが30日未使用・キャッシュ5MB以上・週5件上限で自動 disable する - PROTECTED リストに LSP・インフラ系・既知誤検出を必ず入れる。入れないと必要なプラグインが消える
-
cleanup-plugin-cache.shがキャッシュを.disabled-cache/へアーカイブ。30日後のpurgeで物理削除 - launchd
Weekday=0 / Hour=6 / Minute=45で毎週日曜06:45に無人実行
次回は、これで確保したコンテキスト余裕を確認するために作った statusline のリアルタイムコンテキスト使用率メーター の話を書きます。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
- 制作物・記事は bokuwalily.com にまとめています🖥️
- AIで「寝てても回る仕組み」を作って月120万にした話は noteの有料記事 に💰
- OSS: github.com/bokuwalily 🐙
- 最新情報・お問い合わせは X @bokuwalily へ🌍
皆さんの ❤️ やシェアが励みになります!