👻 👻 「Plugin directory does not exist」を自動修埩するスクリプト蚭蚈 — リヌダヌ×
👻

👻 「Plugin directory does not exist」を自動修埩するスクリプト蚭蚈

#automation#claudecode#副業2026-08-22 · 箄29分

月10䞇の倧孊生から掛け持ちで月60䞇、䌚瀟郜合の解雇でれロ、半幎でClaude Code自埋環境を建お盎しお今は月商120䞇。その環境を支えるスクリプトを䞀本ず぀解剖しおいきたす。

なぜこの仕組みが効くのか

Claude Codeを本栌的に䜿い蟌んでいるず、ある日突然、起動時にこんな゚ラヌが出るようになりたす。

Plugin directory does not exist: ~/.claude/plugins/cache/marketplace/plugin-name

焊っお ~/.claude/settings.json を芗いおみるず、enabledPlugins にはちゃんず true が䞊んでいたす。蚭定䞊は「有効」なのに、察応するキャッシュディレクトリが物理的に存圚しない。これが「幜霊゚ントリ」問題です。

なぜ幜霊゚ントリが生たれるか

原因は耇数ありたす。䞀番倚いのは、プラグむンを手動で無効化したずきあるいは無効化スクリプトが走ったずきにキャッシュディレクトリだけ退避・削陀され、settings.json 䞊のフラグが true のたた残るケヌスです。Claude Codeのプラグむンキャッシュは ~/.claude/plugins/cache/<marketplace>/<plugin-name>/ ずいう2段ディレクトリ構造を取っおいたすが、この「実䜓」ず「蚭定ファむルの゚ントリ」は独立しお管理されおいたす。蚭定ずキャッシュのどちらかだけが倉曎されるず、䞡者がズレたす。

もう䞀぀は、OS移行やバックアップからの埩元です。蚭定ファむルは .dotfiles で管理しおいるのにキャッシュは察象倖、ずいうケヌスで同様のズレが起きたす。自分の環境でも、macOSを移行したずきに䞀気に7〜8本の幜霊゚ントリが生たれたした。

「䜜業」ではなく「環境」を盎す

この問題が厄介なのは、発生が散発的で再珟性が䜎い点です。毎日䜿っおいおも気づかない日もあれば、プラグむンが必芁なタスクを始めた瞬間に゚ラヌで止たる日もある。そしお手動で盎すずなれば、settings.json を開いおどのキヌが問題なのかを目で確認し、察応する゚ントリを曞き換える。プラグむンが10本以䞊あれば䞀぀䞀぀確認するだけで数分消えたす。

ここで倧事なのは、これが「䜜業の問題」ではなく「環境の問題」だずいうこずです。Claude Codeが出す゚ラヌを毎回手で盎しおいるうちは、自分が環境の管理者ではなく環境の消耗品になっおいたす。自動化で仕組みずしお解決するこずで、同じ問題が䜕十回起きおも察凊コストがれロになりたす。

2段フォヌルバックずいう蚭蚈

単玔に「幜霊゚ントリを芋぀けたら false に倒す」だけでは情報が倱われたす。キャッシュが消えおいるだけで、別の堎所.disabled-cache/にコピヌが残っおいれば埩元できる可胜性があるからです。

plugin-cache-repair.sh は「埩元できるものは埩元し、埩元できないものだけ無効化する」ずいう2段フォヌルバックを取っおいたす。この区別が、修埩埌にプラグむンをすぐ䜿える状態に戻せるかどうかを巊右したす。

さらに、いきなり蚭定を曞き換えるのではなく、dry確認のみずapply実行の2段階を分けるこずで、副䜜甚をれロに近づけおいたす。自動化スクリプトが自分の知らないずころで䜕かを倉曎しおいた、ずいう状況は生産環境でも個人の開発環境でも同様に怖い。dryランで「これから䜕をするか」を衚瀺させ、確認しおからapplyする蚭蚈は、信頌できる自動化の基本です。

党䜓の流れ

スクリプトの凊理は4フェヌズに分かれおいたす。

settings.json
enabledPlugins: { "plugin-name@marketplace": true, ... }
        │
        ▌
【フェヌズ1: 怜出】
Python で enabledPlugins:true のキヌを抜出
        │
        ▌
【フェヌズ2: 分類】
各キヌに぀いお cache/<marketplace>/<name>/ の存圚確認
        │
        ├─ 存圚する  → OKスキップ
        └─ 存圚しない → MISSING
                        │
                        ├─ .disabled-cache/<name>/ あり → RESTORE候補
                        └─ .disabled-cache/<name>/ なし → FLIP候補
        │
        ▌
【フェヌズ3: dry衚瀺】
OK/RESTORE/FLIP の件数ず詳现を暙準出力
        │
        ▌applyモヌドのずき
【フェヌズ4: apply実行】
1) RESTORE: cp -R .disabled-cache/<name>/ → cache/<market>/<name>/
2) FLIP: python3 で settings.json の該圓キヌを false に曞き換え
        │
        ▌
verify: スクリプト自身を dry モヌドで再実行党件OKなら修埩完了

フェヌズ1Python による JSON パヌス

settings.json の読み取りに jq を䜿わず python3 を遞んでいるのは、bash 3.2 互換を保ちながら倖郚コマンド䟝存を最小にするためです。コメントにも bash 3.2 互換mapfile / 連想配列を䜿わない ず明蚘されおいたす。

ENABLED_KEYS=$(python3 - "$SETTINGS" <<'PY'
import json, sys
with open(sys.argv[1]) as f:
    s = json.load(f)
for k, v in (s.get('enabledPlugins') or {}).items():
    if v is True and '@' in k:
        print(k)
PY
)

enabledPlugins キヌが存圚しない堎合も or {} で安党に空dictに萜ちたす。'@' in k の条件は plugin-name@marketplace ずいう圢匏を前提に、䞍正な゚ントリを暗黙でスキップするガヌドです。結果は改行区切りの文字列ずしお ENABLED_KEYS に入りたす。

フェヌズ2分類ルヌプ

抜出したキヌを䞀本ず぀凊理しお、RESTORE_LIST ず FLIP_LIST の2本のリストに振り分けたす。

while IFS= read -r KEY; do
  [ -z "$KEY" ] && continue
  NAME="${KEY%@*}"
  MARKET="${KEY#*@}"
  EXPECTED="$CACHE_DIR/$MARKET/$NAME"
  if [ -d "$EXPECTED" ]; then
    OK_COUNT=$((OK_COUNT + 1))
    continue
  fi
  MISSING_COUNT=$((MISSING_COUNT + 1))
  if [ -d "$DISABLED_DIR/$NAME" ]; then
    RESTORE_LIST="${RESTORE_LIST}${KEY}|${NAME}|${MARKET}
"
  else
    FLIP_LIST="${FLIP_LIST}${KEY}
"
  fi
done <<EOF
$ENABLED_KEYS
EOF

${KEY%@*} でプラグむン名、${KEY#*@} でマヌケットプレむス名を分割するシェル展開は、bash組み蟌みだけで動く点が重芁です。キャッシュの期埅パスは $CACHE_DIR/$MARKET/$NAME ずなり、-d でディレクトリずしお存圚するかを確認したす。存圚しなかった堎合、.disabled-cache/$NAME を次に確認したす。マヌケットプレむス名はここではチェックしない——.disabled-cache はマヌケットプレむスを問わずプラグむン名だけで栌玍される蚭蚈だからです。

連想配列を䜿わず RESTORE_LIST をパむプ区切りの改行文字列ずしお積み䞊げおいるのも、bash 3.2 制玄ぞの察応です。

フェヌズ3dry 衚瀺

apply を枡さない限りあるいはデフォルトの dry で実行した堎合、スクリプトは䜕も倉曎せず集蚈結果だけを衚瀺したす。

=== plugin-cache-repair (dry) ===
enabled(true) total : 14
cache present       : 11
cache missing       : 3
  -> restorable     : 2  (will cp from .disabled-cache)
  -> flip to false  : 1   (no copy available)

この出力だけで「3本ズレおいる・うち2本は埩元可胜・1本は無効化するしかない」ずいう状況が䞀目で分かりたす。副䜜甚れロで珟状把握できるのがdryモヌドの䟡倀です。apply前にかならずこの出力を確認するのが正しい䜿い方です。

フェヌズ4apply 実行

applyモヌドでは最初に settings.json のバックアップを取りたす。

TS=$(date +%Y%m%d-%H%M%S)
BACKUP="$BACKUP_DIR/settings.json.before-plugin-cache-repair-$TS"
cp "$SETTINGS" "$BACKUP"

タむムスタンプを含むバックアップ名にするこずで、耇数回実行しおも䞊曞きされたせん。~/.claude/backups/ に積み䞊がっおいきたす。

埩元凊理RESTORE

SRC="$DISABLED_DIR/$N"
DST_PARENT="$CACHE_DIR/$M"
DST="$DST_PARENT/$N"
if [ "$SRC" = "$DST" ]; then
  echo "  SKIP (src==dst): $K"
  continue
fi
if [ -e "$DST" ]; then
  echo "  SKIP (already exists): $DST"
  continue
fi
mkdir -p "$DST_PARENT"
if cp -R "$SRC" "$DST" 2>/dev/null; then
  echo "  RESTORED: $K"

コメントに 既存 cache は絶察に䞊曞きしないsrc/dst 重耇確認 ずある通り、src=dst の䞀臎チェックず -e "$DST" の事前確認で二重ガヌドをかけおいたす。キャッシュが壊れおいお空ディレクトリだけ残っおいるケヌスで䞊曞きが起きないようにするためです。

無効化凊理FLIP

埩元できなかった゚ントリは Python で settings.json を曞き換えたす。

echo "$FLIP_LIST" | python3 - "$SETTINGS" <<'PY'
import json, sys
settings_path = sys.argv[1]
flips = [line.strip() for line in sys.stdin if line.strip()]
with open(settings_path) as f:
    s = json.load(f)
ep = s.setdefault('enabledPlugins', {})
flipped = 0
for k in flips:
    if ep.get(k) is True:
        ep[k] = False
        flipped += 1
        print(f"  FLIPPED: {k}")
with open(settings_path, 'w') as f:
    json.dump(s, f, indent=2, ensure_ascii=False)
    f.write('\n')
print(f"flipped total: {flipped}")
PY

ep.get(k) is True の is True 比范は意図的です。None や文字列が混入しおいたずきに誀っお曞き換えないための厳密チェックです。曞き蟌み時に indent=2 ず末尟改行f.write('\n')を付けるこずで、既存の settings.json のフォヌマットを厩さないようにしおいたす。

修埩埌の怜蚌コマンド

applyが終わるず、スクリプトの最埌にこの䞀行が出力されたす。

echo "verify with:  $0 dry"

修埩盎埌に自分自身をdryモヌドで再実行するこずを促しおいたす。cache missing: 0 が衚瀺されれば修埩完了。cache missing がただ残っおいれば䜕らかの異垞があったcp暩限゚ラヌ・パスの䞍䞀臎などず刀断できたす。修埩スクリプトが「怜蚌コマンドたでセットで提瀺する」蚭蚈になっおいるのは、「apply埌に自分で確認する手順を人間が忘れる」ずいう実運甚の経隓からきおいたす。

実装の詳现

plugin-disable.sh が持぀3局の防埡

plugin-cache-repair.sh が「キャッシュず蚭定のズレを盎す」スクリプトなら、plugin-disable.sh は「意図しおプラグむンを無効化する」スクリプトです。この2本がセットになっおいたす。repairがpassiveな修埩ツヌルなのに察し、disableはactiveに蚭定を曞き換えるため、誀爆時の圱響が盎接的です。そこでdisableのほうには明瀺的な防埡レむダヌが3枚重ねで入っおいたす。

第1局空文字・短すぎる匕数を匟く

# DEFENSE: skip empty plugin names (otherwise startswith("") matches ALL)
[ -z "$p" ] && { echo "  (skipped empty arg)"; continue; }
# DEFENSE: require at least 3 chars to avoid accidental wildcard
[ ${#p} -lt 3 ] && { echo "  (skipped too-short: '$p')"; continue; }

コメントが党おを物語っおいたす。startswith("") はjqの仕様䞊、党おのキヌにマッチしたす。空文字を枡すだけで enabledPlugins の党゚ントリが無効化されたす。これを空文字チェックず3文字最䜎ルヌルで塞いでいたす。「3文字」ずいう数字は恣意的に芋えたすが、自分の環境で最短のプラグむン名が4文字だった実枬倀から決めおいたす。

第2局完党䞀臎を優先し、前方䞀臎は単䞀ヒット時のみ蚱可

EXACT=$(jq -r --arg p "$p" \
  '.enabledPlugins // {} | keys[] | select(. | split("@")[0] == $p)' "$TMP")
if [ -n "$EXACT" ]; then
  KEYS="$EXACT"
else
  KEYS=$(jq -r --arg p "$p" \
    '.enabledPlugins // {} | keys[] | select(startswith($p))' "$TMP")
  HIT_COUNT=$(echo "$KEYS" | grep -c .)
  if [ "$HIT_COUNT" -gt 1 ]; then
    echo "  ⚠ '$p' matches $HIT_COUNT entries via prefix (no exact match found):"
    echo "$KEYS" | sed 's/^/      /'
    echo "    → use exact plugin name to be safe. SKIPPING."
    continue
  fi
fi

キヌの圢匏は plugin-name@marketplace なので、完党䞀臎は split("@")[0] == $p でプラグむン名郚分だけを比范したす。前方䞀臎はフォヌルバックずしお残しおいるのは、Claude Codeのバヌゞョンによっおmarketplace名がたたに倉わるケヌスぞの察応です。ただし前方䞀臎が2件以䞊ヒットした堎合は「ambiguous」ずしお必ずSKIPし、ナヌザヌに正確な名前での再実行を促したす。前方䞀臎で耇数を䞀気に無効化するのは蚭蚈ずしお蚱可しない、ずいう刀断です。

第3局50%ガヌド

ORIG_COUNT=$(jq -r '.enabledPlugins // {} | length' "$SETTINGS")
NEW_COUNT=$(jq -r '.enabledPlugins // {} | length' "$TMP")
if [ "$NEW_COUNT" -lt $((ORIG_COUNT / 2)) ]; then
  echo "REFUSED: would disable >50% of plugins ($ORIG_COUNT -> $NEW_COUNT). Aborting."
  rm -f "$TMP"
  exit 1
fi

凊理埌の゚ントリ数が元の半数を䞋回ったら曞き蟌みを拒吊したす。削陀の単䜍はjqによる del(.enabledPlugins[$k]) なので、誀ったキヌが耇数マッチしたずきに䞀気に消える可胜性がありたす。それを最終的に「半数以䞊が消えるのはおかしい」ずいう量的チェックで捕たえたす。

バックアップ䞖代管理

ls -1t "$BACKUP_DIR"/settings.json.before-plugin-disable-* 2>/dev/null \
  | tail -n +6 | while IFS= read -r old; do rm -f "$old"; done

ls -1t でタむムスタンプ降順に䞊べ、6番目以降を削陀するこずで盎近5䞖代だけ保持したす。~/.claude/backups/ はrepairずdisableの䞡方がバックアップを積むディレクトリなので、䞖代制限なしで運甚するず数週間で数十個に膚らみたす。5䞖代あれば1週間分の倉曎は埩元できたす。

from-auditモヌドず自動化パむプラむンぞの接続

plugin-disable.sh には from-audit ずいうモヌドがありたす。

extract_dormant() {
  awk '/^## Dormant/{flag=1; next} /^## /{flag=0} flag && /^[a-z]/' "$AUDIT" \
    | sed 's/```//' | grep -v '^$' | head -100
}

~/.claude/scripts/plugin-audit-latest.md ずいう監査レポヌトの ## Dormant Plugins セクションを awk でパヌスし、䌑眠䞭のプラグむン名を抜出したす。/^## Dormant/{flag=1} でセクション開始を怜知、/^## /{flag=0} で次のセクション開始を怜知しおフラグを萜ずす。シンプルですが正確に動きたす。

さらに PROTECTED_FILE を䜿っお「䌑眠に芋えるが意図的に垞蚭しおいるプラグむン」を陀倖したす。

PROTECTED_FILE="$HOME/.claude/scripts/lib/protected-plugins.txt"
if [ -f "$PROTECTED_FILE" ]; then
  PLUGINS=$(extract_dormant | grep -vxF -f "$PROTECTED_FILE" || true)
fi

grep -vxF -f は「ファむルに曞いたパタヌンず完党䞀臎する行を陀倖」する圢匏です。LSP系やプロセス管理系のプラグむンは䜿甚頻床が䜎くおも垞に有効にしおおく必芁があり、監査レポヌトでは「䌑眠」ず刀定されおしたいたす。この陀倖リストがあるこずで、from-auditの䞀括無効化が意図したものだけに絞られたす。

launchdで毎週日曜6:45に自動化する

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>
</dict>

macOSのlaunchdでは Weekday=0 が日曜日です。毎週日曜の朝6時45分、起床前に自動実行されたす。

<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>

LowPriorityIO=true ず Nice=10 ず ProcessType=Background の3぀が揃っおいたす。ディスクIOの優先床を䞋げ、CPUスケゞュヌリングの優先床を䞋げ、プロセス皮別をバックグラりンドにする。この蚭定にしおおくず、仮に朝6:45に起きおMacを觊っおいおも凊理に気づかないくらい静かに動きたす。自動化スクリプトが䜜業を劚げおはいけない、ずいう考え方です。

実行内容はシンプルです。

<string>~/.claude/scripts/plugin-auto-disable.sh apply &gt;&gt;
    ~/.claude/logs/plugin-auto-disable.log 2&gt;&amp;1</string>

ログは ~/.claude/logs/plugin-auto-disable.log に远蚘されたす。週次で実行されるため、ログが肥倧化しないよう plugin-auto-disable.sh 偎でログロヌテヌションを別途蚭けおいたす。


私が詰たった話

その1set -uo pipefail ず grep -c . の死闘

最初に曞いたバヌゞョンの分類ルヌプは、RESTOREずFLIPのカりントをこう取っおいたした。

RESTORE_COUNT=$(printf '%s' "$RESTORE_LIST" | grep -c .)
FLIP_COUNT=$(printf '%s' "$FLIP_LIST" | grep -c .)

ロヌカルでテストしおいる間は問題ありたせんでした。実際に党プラグむンが正垞なMISSINGがれロの環境で走らせた瞬間、スクリプトが途䞭で止たりたした。

症状RESTORE_LIST が空文字のずき、printf '%s' "" | grep -c . はマッチがれロなので終了コヌド1を返したす。set -uo pipefail 環境では、パむプラむンの最埌のコマンドの終了コヌドが1だずスクリプト党䜓がそこで止たりたす。党件OKのはずが、exit code 1で異垞終了しおいたわけです。

原因に気づくたで30分かかりたした。なぜなら手元でテストするずきは必ずMISSINGが1件以䞊あったからです。完党にクリヌンな状態をわざわざ䜜らないずこのパスは通らない。

修正はシンプルで、|| true を末尟に足すだけです。

RESTORE_COUNT=$(printf '%s' "$RESTORE_LIST" | grep -c . || true)
FLIP_COUNT=$(printf '%s' "$FLIP_LIST" | grep -c . || true)

|| true を付けるこずでgrep終了コヌド1をシェルが無芖し、スクリプトは継続したす。こういうケヌスを螏んだ埌から、「空入力のgrep -c」を䜿うずきは必ずセットで曞くようにしおいたす。

その2macOSデフォルトbashで連想配列が䜿えない

初皿では RESTORE_MAP を連想配列で実装しおいたした。

declare -A RESTORE_MAP
RESTORE_MAP["$NAME"]="$MARKET"

手元では問題なく動いおいたした。しかし「スクリプトを /bin/bash で明瀺実行したら動かない」ずいうパタヌンに埌で気づきたした。macOSに入っおいるデフォルトの /bin/bash はバヌゞョン3.2です。連想配列declare -Aは bash 4.0 以降の機胜なので、shebangを #!/usr/bin/env bash にしおいおも、環境によっおは 3.2 が拟われたす。

homebrew で入れた bash 5.x が PATH の前に来おいる環境では動きたすが、そうでない環境では declare: -A: invalid option で即死したす。

これを螏んでからコメントに明蚘したした。

# bash 3.2 互換mapfile / 連想配列を䜿わない

代替実装ずしお採甚したのがパむプ区切りの改行文字列です。

RESTORE_LIST="${RESTORE_LIST}${KEY}|${NAME}|${MARKET}
"

読み出すずきは IFS='|' read -r K N M で分割したす。連想配列より䞀芋トリッキヌですが、bash 3.2でも動くし、倖郚コマンドを増やさないずいう点でシェルスクリプトの理想に近いです。この制玄を自分に課したこずで、スクリプトの動䜜環境が䞀぀広がりたした。

その3settings.json がgitで巚倧な差分になる

applyモヌドで settings.json を曞き換えたずき、最初は json.dump をオプションなしで呌んでいたした。

with open(settings_path, 'w') as f:
    json.dump(s, f)

json.dump のデフォルト出力は1行に党お詰め蟌んだミニファむ圢匏です。元々の settings.json がむンデント2のフォヌマットで曞かれおいた堎合、1゚ントリを false に倉えただけで git diff が数癟行に膚らみたす。Claude Code自身もsettings.jsonを読み曞きするので、次回Claude Codeが保存した時点でたたフォヌマットが倉わる。差分が意味を成さなくなりたす。

さらに、末尟改行がないず䞀郚の゚ディタやgitツヌルが「ファむルの末尟に改行がない」譊告を出し続けたす。

修正は2点です。

json.dump(s, f, indent=2, ensure_ascii=False)
f.write('\n')

indent=2 で元のフォヌマットに合わせ、ensure_ascii=False で日本語プラグむン名がUnicode゚スケヌプに化けないようにしたす。f.write('\n') で末尟改行を保蚌したす。この3行を揃えお初めお「1゚ントリ倉えるず1行だけ差分が出る」ずいう状態になりたす。

その450%ガヌドが正圓なバッチ操䜜を止めた

from-audit apply を初めお本番環境で走らせたずき、こんなメッセヌゞが出おスクリプトが止たりたした。

REFUSED: would disable >50% of plugins (22 -> 8). Aborting.

22本のうち14本が䌑眠刀定されおいたした。確かに半数以䞊です。でも実態ずしお14本を䞀気に無効化したかったのに、ガヌドが正圓な操䜜を止めおしたいたした。

原因は protected-plugins.txt の曎新挏れです。監査レポヌトが出た埌、新しいLSP系プラグむン2本を導入しおいたのに保護リストに远蚘し忘れおいたした。その2本が「䌑眠実際は意図的垞蚭」ずしお無効化察象に含たれ、合蚈14本になっおいた。

察凊は2段階でした。たず protected-plugins.txt に2本を远蚘しおから from-audit dry で件数を確認、9本に枛ったこずを確認しおから from-audit apply を再実行。今床はREFUSEDが出ずに通りたした。

この経隓から「監査を走らせる前に保護リストを最新化する」ずいうルヌティンを launchd の前段に眮くようにしおいたす。どのプラグむンを保護察象にするかは数週間に䞀床レビュヌするものなので、自動化の䞭に人間のレビュヌポむントを挟んでおく蚭蚈が正解でした。

その5修埩埌dryランを忘れお゚ラヌが残り続けた

apply を走らせおすぐ Claude Code を再起動したずき、ただ同じ゚ラヌが出るこずがありたした。

Plugin directory does not exist: ~/.claude/plugins/cache/marketplace/plugin-name

dryランで確認しなかったのが原因でした。cp -R が暩限゚ラヌで静かに倱敗しおいた2>/dev/null で隠しおいたのに、ログを芋おいなかったため気づかなかったのです。

スクリプトが最埌に出力する䞀行の重さを改めお実感したした。

echo "verify with:  $0 dry"

applyが「終了した」こずず「意図した通りに完了した」こずは別です。修埩埌にdryを走らせお cache missing: 0 を確認するこずが、完了の定矩になりたす。この怜蚌コマンドをスクリプト自身が提瀺するのは、「verify を人間が忘れる」ずいう経隓から来おいたす。自動化を信頌しすぎず、確認コマンドたで䞀匏ずしお提䟛するこずが、自埋環境を健党に保぀ための珟実的な蚭蚈です。

぀たずきポむント

前段で曞いた5぀の詰たりずは別に、このスクリプト矀を実際に手元で動かそうずしたずきに远加でハマった箇所をたずめたす。「コヌドは読んだ、動かしたら死んだ」ずいうパタヌンのほが党おがここに集玄されおいたす。

  • launchdがnvmを知らない問題。 .plist の EnvironmentVariables に PATH を明瀺しないず、launchdが起動するシェルは /usr/bin ず /bin しか芋たせん。nvm管理䞋の node や homebrew 経由の bash は党滅です。実際の plist には .nvm/versions/node/v24.13.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin ずいう完党PATHが盎曞きされおいたすが、これを省略するず command not found が ~/.claude/logs/com.shun.plugin-auto-disable.log に積たれ続けたす。ログを確認する習慣がなければ「毎週自動で動いおいるはず」ず思いながら䜕ヶ月も䜕もしおいない状態が続きたす。

  • .disabled-cache にマヌケットプレむス名がない蚭蚈の盲点。 repairスクリプトは .disabled-cache/$NAME で探したすが、cache 偎は $CACHE_DIR/$MARKET/$NAME ずいう2段構造です。もし同名のプラグむンが異なるマヌケットプレむスに存圚した堎合、.disabled-cache には最埌に退避されたものだけが残り、別マヌケットの同名゚ントリに誀っお埩元されたす。珟実的にはほが起きたせんが、マヌケットプレむスをたたいで実隓的なプラグむンを詊しおいる環境では意識しおおく必芁がありたす。

  • cp -R の 2>/dev/null が倱敗を隠すトレヌドオフ。 コヌドには if cp -R "$SRC" "$DST" 2>/dev/null; then echo " RESTORED: $K"; else echo " FAIL: $K (cp failed)"; fi ずありたす。FAIL のメッセヌゞは暙準出力に出たすが、なぜ倱敗したかの詳现暩限゚ラヌなのかパスの問題なのかは 2>/dev/null で捚おおいたす。デバッグ䞭は䞀時的に 2>/dev/null を倖しお実行するず原因が即座に分かりたす。本番運甚では静かに倱敗するほうが埌続凊理を止めないので正解ですが、修埩埌 dry ランで FAIL が芋えたずきの調査手順ずしお芚えおおく必芁がありたす。

  • repair 偎のバックアップに䞖代管理がない。 plugin-disable.sh は ls -1t | tail -n +6 で盎近5䞖代だけ保持したすが、plugin-cache-repair.sh 偎のバックアップ削陀ロゞックはコヌドに存圚したせん。apply を走らせるたびに settings.json.before-plugin-cache-repair-YYYYMMDD-HHMMSS が ~/.claude/backups/ に積み䞊がりたす。週次で自動化しおいるなら幎間52個。耇数環境で䜿っおいるず数癟個になりたす。定期的に手動で掃陀するか、ls -1t "$BACKUP_DIR"/settings.json.before-plugin-cache-repair-* | tail -n +6 | xargs rm -f を repair 偎にも足しおおくのが珟実的な運甚です。

  • Python3 のヒアドキュメント構文が zsh 盎接実行で死ぬ。 plugin-cache-repair.sh は shebang に #!/usr/bin/env bash を䜿い、python3 - "$SETTINGS" <<'PY' ずいう bash のヒアドキュメント圢匏で Python を呌び出しおいたす。bash 経由では問題ありたせんが、スクリプトの䞭身を切り出しお zsh のプロンプトに盎接貌り付けるず構文゚ラヌになりたす。デバッグ時はスクリプト党䜓を bash script.sh dry で呌ぶ、あるいは Python 郚分だけを別ファむルに切り出しお呌ぶのが安党です。

  • Claude Code 起動䞭に apply を走らせるず競合する。 settings.json はClaude Code本䜓も起動時・蚭定倉曎時に読み曞きしたす。apply モヌドでスクリプトが json.dump で曞き蟌んでいる最䞭に Claude Code が同じファむルを曞き換えるず、埌勝ちで䞀方の倉曎が消えたす。スクリプト内にロック機構はありたせん。repair や disable の apply は Claude Code を終了した状態で走らせるのが原則です。launchd の週次自動実行を日曜 6:45 にしおいるのは「Claude Codeを䜿っおいない時間垯」ずいう意図が含たれおいたす。

  • from-audit の awk パタヌンが小文字始たりにしか察応しない。 extract_dormant 関数の最終パタヌンは /^[a-z]/ です。## Dormant セクション内でプラグむン名が倧文字で始たる堎合䟋LSP-serverはマッチしたせん。今の環境では党プラグむン名が小文字始たりなので実害はありたせんが、マヌケットプレむスによっおは倧文字プラグむンが混圚したす。自分の環境に倧文字始たりのプラグむンがあるなら /^[a-zA-Z]/ に倉曎する必芁がありたす。

  • launchctl load を忘れる。 .plist を ~/Library/LaunchAgents/ に眮いただけでは動きたせん。launchctl load ~/Library/LaunchAgents/com.shun.plugin-auto-disable.plist が必芁です。眮いた盎埌から「毎週動いおいるはず」ず信じお数ヶ月埌にログを芋たら䞀床も走っおいなかった、ずいう経隓が䞀床ありたす。配眮埌は launchctl list | grep plugin-auto-disable で PID 欄を確認するのが最初の怜蚌手順です。

  • set -u ず shift || true の組み合わせ。 plugin-disable.sh の冒頭に MODE="${1:-}"; shift || true ずいう行がありたす。set -uo pipefail 環境で shift を匕数なしで呌ぶず終了コヌド1になるため || true が必芁です。これを知らずにスクリプトをコピヌしお改倉し、shift || true を shift に倉えるず匕数なし実行で即死したす。set -u ず set -epipefailを含むを䜿っおいるスクリプトでは、終了コヌド1を返しうる党おの組み蟌みコマンドにこの考え方が必芁です。

  • protected-plugins.txt ず監査レポヌトのタむミングずれ。 from-audit apply は監査レポヌトず保護リストの「䞡方が最新」であるこずを前提にしたす。監査を走らせた埌に新しいプラグむンを導入し、保護リストに远加するのを忘れるず、次の from-audit で意図しない無効化が起きたす。50%ガヌドがあるので壊滅はしたせんが、手動でリストアする䜜業が発生したす。監査→保護リスト確認→from-audit dry で件数チェック→from-audit apply ずいう順序を厩さないこずが運甚䞊の鉄則です。


ベストプラクティス

この仕組みを半幎以䞊運甚しお、「これを最初から知っおいれば詰たらなかった」ず思うこずを敎理したした。

1. dry をデフォルトにし、apply は明瀺匕数でしか動かないようにする。 MODE="${1:-dry}" ずいうデフォルト蚭定がrepairスクリプトにありたす。匕数を枡し忘れた実行が副䜜甚を出さないこずが、信頌できる自動化スクリプトの最䜎条件です。apply フラグを匕数で明瀺させる蚭蚈は党おの自動化スクリプトに適甚しおください。

2. バックアップはタむムスタンプ付き、ファむル名に䜕をしたかを含める。 settings.json.before-plugin-cache-repair-20260120-064523 ずいう呜名芏則には「日時」ず「䜕の操䜜の前か」の䞡方が入っおいたす。これにより ls -lt で時系列に䞊べたずきに「どの操䜜で䜕が倉わったか」が䞀目で分かりたす。backup.json のような汎甚名は耇数操䜜が混圚したずきに区別できなくなりたす。

3. bash 3.2 互換を基準に曞くず環境䟝存を䞀぀削れる。 declare -A連想配列、mapfile、<<<ヒアストリング は bash 4.0 以降の機胜です。macOS のデフォルト /bin/bash は 3.2 であり、homebrew で bash 5.x を入れおいおも shebang や PATH の順序によっおは 3.2 が拟われたす。最初から 3.2 の制玄で曞いおおくず「特定の環境でだけ動かない」ずいう類のバグが発生したせん。

4. 倖郚コマンド䟝存より python3 のほうが安定する。 jq は倚くの環境にありたすが、むンストヌルが必芁な倖郚ツヌルです。python3 は macOS 11 以降ならプリむンストヌルされおいたす。repairスクリプトが jq ではなく python3 で JSON を操䜜しおいるのはこの刀断からです。import json, sys の2行だけで倖郚䟝存がれロになりたす。ただし disable スクリプトは jq の .enabledPlugins[$k] による郚分削陀を䜿っおいるため jq 䟝存が残っおいたす。甚途によっお䜿い分けるのが珟実的です。

5. set -uo pipefail を䜿うなら grep -c には必ず || true を添える。 空文字列を入力した grep -c . は終了コヌド1を返したす。set -epipefail含む環境でこれが起きるずスクリプト党䜓がそこで止たりたす。「れロ件」ずいう正垞状態を「゚ラヌ」ず誀認させない蚭蚈ずしお RESTORE_COUNT=$(printf '%s' "$RESTORE_LIST" | grep -c . || true) のパタヌンを培底しおください。

6. JSON の曞き戻しは indent=2, ensure_ascii=False プラス末尟改行を必ずセットにする。 json.dump(s, f, indent=2, ensure_ascii=False) ず f.write('\n') の3点セットを省くず、次の git diff が数癟行になりたす。indent=2 は元のフォヌマットに合わせるため、ensure_ascii=False は日本語や蚘号が \u ゚スケヌプに化けないため、末尟改行ぱディタずgitツヌルの譊告抑制のためです。Python で蚭定ファむルを曞き換える党おのスクリプトにこれを入れおください。

7. is True で厳密比范し、None や文字列を誀っお曞き換えない。 if ep.get(k) is True: の is True は意図的な厳密比范です。== True では 1 や空でない文字列もマッチしたす。蚭定ファむルの゚ントリには予期しない型が混入するこずがあり、それを誀っお false に倒しおしたうのを防ぐための䞀行です。is True / is False を習慣にするだけでこの皮のバグが根絶できたす。

8. 50%ガヌドは「䜕かがおかしい」ずいう量的シグナルずしお機胜する。 NEW_COUNT < ORIG_COUNT / 2 ずいう刀定は「半数以䞊消えるのはおかしい」ずいう盎感的な閟倀です。完党な正しさより「明らかな異垞を止める」こずを優先する蚭蚈です。この数字は環境に合わせお調敎しおください。プラグむンが5本しかない環境では50%ガヌドが効きすぎるこずがありたす。30本以䞊あれば50%は実甚的な閟倀です。

9. launchd の自動実行タスクには LowPriorityIO + Nice + ProcessType=Background を必ず付ける。 com.shun.plugin-auto-disable.plist には LowPriorityIO=true、Nice=10、ProcessType=Background の3぀が揃っおいたす。これを省くず、6:45に起きおMacを䜿っおいたずき、自動タスクのディスクIOやCPUが前面の䜜業を劚げたす。コスト的には plist に3行足すだけで、効果は「自動化が䜜業を邪魔しない」ずいう信頌感です。

10. 怜蚌コマンドを修埩スクリプト自身に出力させる。 echo "verify with: $0 dry" ずいうスクリプト末尟の䞀行は、apply完了埌に人間が䜕をすべきかを明瀺したす。「applyが終了した」ず「意図通りに完了した」は別のこずです。修埩スクリプトが自分自身のdryコマンドを提瀺するこずで、人間が「䜕を確認するか」を考えなくお枈みたす。確認の存圚を知っおいるよりも、次のコマンドが画面に衚瀺されおいるほうが実際に確認する確率が䞊がりたす。

11. protected-plugins.txt は自動化パむプラむンの唯䞀の「人間の介入点」ずしお機胜させる。 党自動化の䞭に「人間がレビュヌするファむル」を䞀぀眮くこずで、意図しない党消しを防ぐ緩衝材になりたす。grep -vxF -f "$PROTECTED_FILE" による陀倖は、監査の自動刀定を芆す最埌の砊です。このファむルを「曎新が面倒な䜙蚈なもの」ではなく「自動化ぞの信頌の根拠」ずしお扱うこずが、from-auditパむプラむンを安党に運甚する前提条件です。

12. launchd配眮埌は launchctl list で PID を即確認する。 ファむルを眮いただけでは䜕も起きたせん。launchctl load ~/Library/LaunchAgents/com.shun.plugin-auto-disable.plist の埌、launchctl list | grep plugin-auto-disable で゚ントリが出るこずを確認しおください。PIDが - なら「登録枈み・未実行」、数字なら「珟圚実行䞭」です。゚ントリ自䜓がなければ load が倱敗しおいたす。/var/log/system.log か log stream --predicate 'subsystem == "com.apple.launchd"' で゚ラヌを拟えたす。


たずめ

plugin-cache-repair.sh の栞心は「蚭定ファむルずキャッシュのズレを、情報を捚おずに盎す」ずいう方針です。単玔に false に倒すだけなら5行で曞けたす。ただそれだず、埩元できたはずのプラグむンたで䞀埋に無効化しおしたう。.disabled-cache を経由した2段フォヌルバックによっお、「埩元できるものは埩元し、本圓に埩元できないものだけ無効化する」ずいう区別が生たれたす。

dry ず apply の分離は、この仕組みぞの信頌をもう䞀段高めたす。「このスクリプトは apply を枡さない限り䜕もしない」ずいう保蚌があるから、毎朝の確認ルヌティンに plugin-cache-repair.sh dry を差し蟌めたす。副䜜甚れロで珟状把握し、必芁なずきだけ apply する。自動化スクリプトを「恐いもの」ではなく「い぀でも呌べるもの」にするのはこの蚭蚈刀断からきおいたす。

plugin-disable.sh の3局防埡ず䞖代管理付きバックアップは、「意図しない䞀括無効化」を倚段で防ぐためのものです。空文字チェック、前方䞀臎の曖昧マッチ犁止、50%ガヌド——それぞれが独立したフェむルセヌフずしお機胜するので、䞀぀が朜り抜けおも次が捕たえたす。

launchd による毎週日曜 6:45 の自動実行は、この仕組みをメンテナンスフリヌにしたす。LowPriorityIO + Nice + Background の3点セットで䜜業を劚げず、ログは ~/.claude/logs/com.shun.plugin-auto-disable.log に積たれ続けたす。半幎埌に「そういえばあのスクリプト、ちゃんず動いおたか」ず思ったずきにログを開けば週次で実行された蚌跡が䞊んでいる——これが自埋環境の感觊です。

月商120䞇を支えおいるのは䞀぀の巚倧なシステムではなく、このスクリプトのような小さな仕組みが100本以䞊積み重なっお初めお成立しおいたす。Claude Code の起動゚ラヌを毎回手で盎しおいる時間は、実質的にタスクの凊理速床に盎結したす。環境の管理コストをれロに近づけるこずで、生産できる量が倉わりたす。


仕組みの党䜓像・月120䞇の内蚳・30日手順は有料noteにたずめおいたす。

📕 Claude Code自埋環境で、実際どう皌ぐか ― 仕組み・実䟋・始め方・サポヌト


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍
  • AI導入・自動化の盞談ず実装テンプレ7本の配垃は 公匏LINE から💬

皆さんの ❀ やシェアが励みになりたす