Plugin not loadedの原因と直し方 — Claude Codeのmanifest・ELOOP・EACCES
Plugin not loaded: its manifest was not readとskills path could not be checked (ELOOP)の違いを整理し、symlinkと権限の原因別に直し方を示します。
/pluginのErrorsタブにPlugin not loaded: its manifest ... was not readと出ている場合、そのプラグインからは何も読み込まれていません。スキルもフックもMCPサーバーも、すべて欠けた状態です。同じ画面にskills path could not be checked: ... (ELOOP)が出ているなら、プラグイン本体は動いていて、skills/だけが落ちています。
2つのエラーは見た目が似ていますが、被害の範囲も直し方も違います。共通する原因はsymlink(シンボリックリンク)とディレクトリの権限です。
2つのエラーは何を落とすのか
メッセージの文言と、読み込みが止まる範囲を並べると次のとおりです。
| メッセージ | 止まる範囲 | 主な原因 |
|---|---|---|
Plugin not loaded: its manifest <path> was not read | 止まる範囲そのプラグインの全部 | 主な原因.claude-pluginかplugin.jsonがプラグイン外を指すsymlink、または権限不足 |
skills path could not be checked: <path> (ELOOP) | 止まる範囲skills/など該当の1パスのみ | 主な原因symlinkの循環、壊れたネットワークマウント、権限不足 |
<component> path escapes plugin directory | 止まる範囲該当の1パスのみ | 主な原因../で外を指す、またはプラグイン外へ抜けるsymlink |
1行目は、マーケットプレイスのプラグインの.claude-plugin/plugin.jsonが、プラグインの内側にあると確認できなかったときに出ます。ローカルのマーケットプレイスにあるプラグインでは、マーケットプレイスの内側にあることも確認されます。確認できないと、そのプラグインからは何も読み込まれません。
2行目は、OSに「このパスはあるか」と問い合わせて、「見つからない」以外のエラーが返ったときのエラーです。skills/フォルダ、monitors/monitors.json、プラグインルートのSKILL.mdのような既定の置き場所が失敗しても、他のコンポーネントは読み込まれます。ただしプラグイン自身のディレクトリが失敗すると、プラグインごと読み込まれません。
存在しないパスではこのエラーは出ません。「ファイルが無い」と「確認できなかった」は別扱いです。
manifestが読まれない場合の直し方
エラー文の末尾には理由が付きます。例えば、ローカルマーケットプレイスの.claude-pluginフォルダが外部のdotfilesを指すsymlinkだと、次の形になります。
Plugin not loaded: its manifest /home/user/my-marketplace/my-plugin/.claude-plugin/plugin.json was not read, because it resolves to /home/user/dotfiles/my-plugin/.claude-plugin/plugin.json, outside the plugin directory and its marketplace directorybecause it resolves toの後ろに実体の場所が書かれていれば、.claude-pluginフォルダかplugin.json自体がそこへ向くリンクです。dotfilesの管理ツールで設定をリンクにしている環境に当てはまる形です。
manifestエラーの切り分け順
- 1
理由の末尾を読む
EACCESなどのエラーコードで終わっているか、resolves toで実体の場所が書かれているかを見ます。 - 2
自分が作者かを決める
入れただけのプラグインなら、メッセージを作者に報告します。自分で保守しているなら次へ進みます。
- 3
リンクを実体に置き換える
.claude-pluginフォルダかplugin.jsonがリンクなら、リンクを削除して指していた内容のコピーに置き換えます。 - 4
再読み込みする
/reload-pluginsを実行するか、Claude Codeを再起動します。
置き換えの操作は、手元のシェルで次のように行えます。my-pluginは読み替えてください。
# .claude-plugin がリンクかどうか確認する
ls -ld my-plugin/.claude-plugin
# リンクなら、指し先の内容で実体に置き換える
cp -RL my-plugin/.claude-plugin my-plugin/.claude-plugin.copy
rm my-plugin/.claude-plugin
mv my-plugin/.claude-plugin.copy my-plugin/.claude-plugincp -RLはリンクをたどって中身をコピーします。元のdotfilesと二重管理になるので、以後の編集はプラグイン側で行うか、プラグインをdotfilesから切り離す運用に変える必要があります。
「ELOOP」は循環、「EACCES」は権限
path could not be checkedのかっこ内は、OSが返したエラーコードです。コードごとの原因は次のとおりです。
ELOOP: パス上のsymlinkが自分自身を指しているか、循環しているEIOまたはESTALE: パスのあるネットワークマウントが壊れているか、古くなっているEACCES: パスより上のディレクトリを辿る権限(実行権限)がない
claude plugin listでは同じエラーがPath not found: /home/user/my-plugin/skills (skills, ELOOP)の形で表示されます。文言が違うだけで、同じ原因です。
ELOOPをMacで再現する
ELOOPは、自分自身を指すリンクを作るだけで再現できます。次のとおり空のディレクトリで試し、Node.jsのstatSyncでエラーコードを見ると確かめられます(macOS、Node.js v20.18.0)。
ln -s skills skills
ls skills/
node -e "try{require('fs').statSync('skills')}catch(e){console.log(e.code)}"lsはToo many levels of symbolic linksと答え、Node.jsはELOOPを返しました。ls -lで見るとskills -> skillsと、自分自身が指し先になっています。プラグインのskills/が同じ状態なら、エラーメッセージに出るコードもELOOPです。
自分を指すリンクを削除して実フォルダを作れば直ります。フォルダを作り直す前に、リンクの指し先をreadlinkで確かめると、意図した共有だったのか、作成ミスだったのかが分かります。
EACCESは「ファイル」ではなく「途中のディレクトリ」を見る
EACCESで見るのは、対象ファイルの読み取り権限ではありません。対象に至るまでの各ディレクトリの実行権限です。symlinkを経由している場合は、リンクを辿った先のディレクトリ列も確認する必要があります。
権限の穴はnamei -lで見つけられます(Linux)。macOSではnameiが使えない環境があるので、ls -ldを上位ディレクトリへ順にたどります。
namei -l /home/user/my-plugin/.claude-plugin/plugin.jsonどこかに実行ビット(x)のないディレクトリがあれば、そこが原因です。chmod u+xで戻します。NASなど別マシンのマウントを経由しているなら、マウント先の権限も見ます。
path escapesとの違いと、symlinkの許される範囲
3つ目のpath escapes plugin directoryは、似たsymlink問題でも、コンポーネント単位で出ます。plugin.jsonやマーケットプレイスのエントリで宣言したパスが、プラグインの外に解決されると出て、その1パスだけが捨てられます。
拒否される対象は3つです。
../shared-utilsのように、書き方の時点で外を指すパス- プラグイン外へ出るsymlink(同一マーケットプレイス内のプラグイン間リンクは除く)
- macOSとLinuxで、パスのどこかにバックスラッシュを含むもの
3つ目は、プラグインの内側に収まるパスでも拒否されます。Windows区切りで書かれたプラグインはWindowsでは読み込まれ、他のOSでは拒否される理由がこれです。./commands/deploy.mdのようにスラッシュで書きます。
プラグイン間でファイルを共有したい場合は、プラグインのディレクトリ内にsymlinkを作る方法が用意されています。キャッシュにコピーされるときの扱いは、リンクの解決先で変わります。
| リンクの解決先 | キャッシュでの扱い |
|---|---|
| プラグイン自身のディレクトリ内 | キャッシュでの扱い相対symlinkのまま残る |
| 同じマーケットプレイス内の別の場所 | キャッシュでの扱い実体としてコピーされる |
| マーケットプレイスの外 | キャッシュでの扱いセキュリティのためスキップされる |
ローカルのパスからインストールしたプラグインや、commandソースのcopyモードでは、プラグイン自身のディレクトリ内に収まるリンクだけが保たれ、残りはスキップされます。
つまり、「外部のdotfilesにあるスキルをリンクで取り込む」構成は、マーケットプレイスから入れるプラグインでは通りません。.claude/rulesにリンクを置く場合の挙動は別のsymlink問題で、プラグインとは仕組みが異なります。
ローカルマーケットプレイスで起きやすい理由
マーケットプレイスのプラグインは、入れ方によって読み込み方が変わります。ローカルのパスから追加したマーケットプレイスの相対パスのプラグインは、キャッシュにコピーされず、マーケットプレイスのフォルダ内からその場で読み込まれます。ソースを編集すると、次のセッション開始か/reload-pluginsで反映され、バージョンを上げる必要もありません。
手元のdotfilesやモノレポを、そのままローカルマーケットプレイスとして使う構成では、手元のリンクがそのまま検査の対象になります。manifest was not readのメッセージに「マーケットプレイスのディレクトリの外」という文言が入るのは、この場合に外側の境界も確かめられるためです。
それ以外のマーケットプレイスのプラグインはキャッシュにコピーされ、その時点でリンクの扱いが前掲の表のルールで決まります。
マーケットプレイスのエントリ自体が拒否される場合
プラグインのエントリ(marketplace.jsonのsource)が、マーケットプレイスの内側に解決できない場合も、別のエラーでプラグインが入りません。claude plugin installはits marketplace entry path does not stay inside the marketplace directoryと答え、すでに入っているプラグインはclaude plugin listでfailed to loadとなり、Plugin source path refusedと表示されます。
直し方は、sourceを./plugins/my-pluginのような、スラッシュ区切りの素直な相対パスで書くことです。途中で通るリンクも、マーケットプレイスの内側を指すようにします。URLで直接追加したマーケットプレイスでは相対パスのエントリが使えないため、gitリポジトリから追加し直すか、別のソース形式を使います。
バージョンによる違い
古いバージョンでは、同じ状況でもエラーが出ませんでした。
- v2.1.265より前: 既定のコンポーネントフォルダを確認できないと、存在しないものとして扱い、エラーなしでそのコンポーネントを抜いたままプラグインを読み込んでいた
- v2.1.257より前: プラグインが宣言したパスの検査は、パスの綴りだけを見ており、symlinkの行き先を見ていなかった
- v2.1.251より前: マーケットプレイスエントリの
commandsパスは、プラグインの外を指していても読み込まれていた
古いバージョンで「エラーは出ないのにスキルだけ無い」場合は、同じ原因が黙って起きていた可能性があります。更新後に初めてエラーが現れるのは、検出が厳しくなったためです。
直したあとの確認
修正後は/reload-pluginsで再読み込みします。コマンドの動きは/reload-pluginsの解説にまとめています。ただしMCPの構成が変わる場合はキャッシュ無効化の警告が出ます。
CIや非対話実行では、読み込み失敗が出力に出ても終了コードは0のままになりがちです。plugin_errorsを失敗条件にする方法はCIでplugin_errorsを検査する記事にあります。
--plugin-dirで渡したディレクトリは、コピーされずその場で読み込まれます。開発中のプラグインを1セッションだけ試すなら、CLAUDE_CODE_PLUGIN_DIRSの使い方が参考になります。
落とし穴
リンクの先が正しくても、権限で落ちることがある
lsでファイルが読めても、Claude Codeを起動するユーザーの権限で途中のディレクトリを辿れなければEACCESになります。sudoで作ったディレクトリや、別ユーザーのホーム配下を指すリンクが典型です。
どの手から試すか
エラー文にPlugin not loadedとあるなら、.claude-pluginとplugin.jsonの実体を確かめます。could not be checkedなら、かっこ内のコードで切り分けます。ELOOPならリンクの循環、EACCESなら途中のディレクトリの実行権限、EIOとESTALEならマウントの状態です。
作者でない場合は、自分で中身を書き換えるより、メッセージ全文を作者に渡すほうが早く済みます。