claude plugin組織同期でbin/ディレクトリが弾かれる原因と直し方
組織同期のエラー「Plugin contains a top-level bin/ directory」は、bin/を持つプラグインだけが拒否される仕様です。scripts/への移し方と、同期で他に止まる条件をまとめます。
claude plugin組織同期でbin/ディレクトリが弾かれる原因と直し方
claude.aiの組織同期でプラグインがPlugin contains a top-level bin/ directoryというエラーで弾かれたら、そのプラグインのルート直下にbin/があります。直し方は、実行ファイルをscripts/などの別ディレクトリへ移し、${CLAUDE_PLUGIN_ROOT}/scripts/<name>で呼び出す形にすることです。同期に失敗するのはbin/を持つプラグインだけで、マーケットプレイスの残りは通常どおり同期されます。
どのプラグインが、どこで弾かれるのか
Team・Enterpriseプランでは、Organization settings > Plugins & skillsにClaude Codeのマーケットプレイス用リポジトリを同期できます。このとき、ルート直下にbin/を持つプラグインはclaude.ai側が受け付けません。エラーの冒頭は次の一文です。
Plugin contains a top-level bin/ directory拒否の範囲は次のとおりです。
- マーケットプレイス同期では、該当プラグインだけが拒否され、ほかのプラグインは同期される
- 同期を使わず、同じ画面から直接アップロードした場合も、
bin/があれば拒否される - 対象は「トップレベルの
bin/」。プラグイン内の別階層にあるディレクトリは、この規則の対象として書かれていない
同期が失敗した場合は、マーケットプレイスのページに失敗した日付と理由が出ます。View affected itemsのリンクから、どのプラグインが原因かをたどれます。
落とし穴
一部のプラグインだけ同期されないまま気づきにくい
マーケットプレイス全体が失敗するのではなく、該当プラグインだけが落ちて残りが同期されます。メンバーの画面には「そのプラグインが無い」状態として現れるため、追加したはずのプラグインが見当たらないときは、まず管理画面の失敗理由を確かめます。
なぜbin/だけが特別扱いなのか
Claude Codeでは、プラグインのbin/に置いたファイルは、プラグインが有効な間だけBashツールのPATHに載ります。Claudeはそれを素のコマンド名で実行できます。
一方、claude.aiとCoworkは、このbin/を持つプラグインをインストールしません。組織設定経由の配布も同じです。つまりbin/は「Claude Code専用の仕組み」で、claude.aiとCoworkには届かない前提で作られています。拒否の理由そのものは、挙げた各ページに書かれていません。
プラグインを組織全体に配るルートには、claude.aiとCoworkのメンバーに届く組織同期と、Claude Codeの管理設定があります。bin/に依存する作りのままでは、前者のルートに乗れません。Skillsを組織へ配る手順はClaudeのSkillsを組織全員に配布する方法にまとめています。
bin/をscripts/に移して参照を書き換える
直す作業は3つです。
bin/を取り除く手順
- 1
実行ファイルを移す
bin/の中身をscripts/へ移します。ディレクトリ名はscripts/でなくても構いません。 - 2
呼び出し側を書き換える
素のコマンド名で呼んでいた箇所を、
${CLAUDE_PLUGIN_ROOT}/scripts/<name>に変えます。 - 3
デフォルトブランチへpushして再同期する
同期はデフォルトブランチを読みます。pushするか、管理画面でRe-syncを選びます。
移動にはgit mvを使うと、履歴を保ったまま済みます。
git mv plugins/deploy-tools/bin plugins/deploy-tools/scripts
git commit -m "fix: bin/ を scripts/ に移す"
git push${CLAUDE_PLUGIN_ROOT}は、プラグインがインストールされた版の絶対パスに解決される変数です。書ける場所は決まっていて、フックのcommandとargs、MCPのstdioサーバーのcommand・args・env、スキルやエージェントのMarkdown本文の中などです。
注意したいのは、Bashツールで実行されるコマンドの環境にはこの変数が入らない点です。スキルの本文に$CLAUDE_PLUGIN_ROOTと書いても、シェル変数としては展開されません。本文には${CLAUDE_PLUGIN_ROOT}の形で書くと、読み込み時にパスへ置き換えられます。
スキルからの呼び出しを書き換える
bin/を前提にした書き方
SKILL.mdの本文:
デプロイ前に deploy-tool --check を実行してください。scripts/を指す書き方
SKILL.mdの本文:
デプロイ前に "${CLAUDE_PLUGIN_ROOT}"/scripts/deploy-tool --check を実行してください。フックから呼ぶ場合は、プラグインのリファレンスにある書き方がそのまま使えます。次の形は、そこに載っている例に沿った書き換えの一例です。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{ "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.sh" }
]
}
]
}
}${CLAUDE_PLUGIN_ROOT}は更新のたびに指す場所が変わります。状態の保存先には使わず、設定やキャッシュは${CLAUDE_PLUGIN_DATA}に置きます。
push前にbin/の有無を機械で確かめる
同期のたびに管理画面で失敗に気づくのは遅いので、リポジトリ側で先に検査すると安全です。次のスクリプトは、marketplace.jsonが相対パスで指すプラグインを調べ、ルート直下にbin/があるものを列挙します。
jq -r '.plugins[].source | strings' .claude-plugin/marketplace.json \
| while read -r p; do
[ -d "$p/bin" ] && echo "top-level bin/ あり: $p"
donestringsで絞っているので、対象は./plugins/deploy-toolsのような相対パスのsourceだけです。githubやgit-subdirなど、別リポジトリを指すオブジェクト形式のsourceは検査に入りません。そちらはリポジトリを開いて、ルート直下を直接確認します。
この検査をCIに組み込み、1件でも出力があれば失敗させる使い方もできます。bin/を足した変更が、レビューの段階で止まります。
同期で止まる条件はbin/だけではない
bin/を直しても同期が通らないときは、リポジトリとプラグインの出どころに別の条件があります。組織同期はClaude Codeの/plugin marketplace addより厳しく、次のルールを課します。
| 見る場所 | 条件 |
|---|---|
| マーケットプレイスのリポジトリ | 条件github.comとgitlab.comでは、privateまたはinternalに限る |
プラグインのsourceの種類 | 条件github、url、git-subdir、または./で始まる相対パス |
metadata.pluginRoot配下の裸の名前 | 条件未対応のsourceとして拒否される。./plugins/deploy-toolsのように書き下す |
| privateなプラグインの出どころ | 条件同じオーナーのgithub.com、GitHub Enterpriseの連携済みリポジトリ、同じGitLabホストなど |
| それ以外の出どころ | 条件github.com、gitlab.com、bitbucket.orgの公開リポジトリのみ |
Claude Codeのマーケットプレイスでは、npm・archive・commandのsourceも書けます。しかし組織同期が受け付ける種類の一覧には、これらが挙がっていません。Claude Codeで動くマーケットプレイスでも、組織同期では別の理由で落ちることがあります。
非公開のプラグインを配りたい場合は、プラグインのフォルダ自体をマーケットプレイスのリポジトリ内に置き、相対パスで参照します。組織同期が配布時にプラグインをパッケージするため、メンバーが別のソースリポジトリにアクセスする必要はありません。
{
"name": "deploy-tools",
"source": "./plugins/deploy-tools"
}リポジトリへのアクセス経路も、ホストごとに違う
bin/を直しても、同期そのものが開始できないことがあります。組織同期はデフォルトブランチを、claude.aiの組織のGitHubまたはGitLab連携で読みます。メンバーのGit認証情報は使われません。
| ホスト | 読み取りに使われるもの |
|---|---|
| github.com | 読み取りに使われるものClaude GitHub App(同期の追加時にインストール) |
| GitHub Enterprise Server | 読み取りに使われるもの組織のGitHub Enterprise App |
| gitlab.comまたは自前のGitLab | 読み取りに使われるものOrganization settings > Git providersに登録したGitLab設定のアクセストークン |
GitLab設定は公開ベータで、プラグインマーケットプレイスの同期にだけ使われます。追加できるのはOwnerです。GitHubでリポジトリが一覧に出ないときは、追加画面のInstall the Claude GitHub Appから、そのリポジトリへのアクセスを許可します。
自動同期をオンにすると、デフォルトブランチへのpushのたびに同期が走ります。GitHubではwebhookがリポジトリに作られます。後からオンにしてNo webhook yetと出たときは、Configure webhookからEnable webhookを選びます。
直したあと、メンバーに届くまで
同期は、管理者がRe-syncを選んだとき、またはSync automaticallyがオンのときのデフォルトブランチへのpushで始まります。時刻による定期同期はありません。タグを打っても、それだけでは何も配られません。
Re-syncでYou synced recentlyと出たら、表示された秒数だけ待って、もう一度選びます。
リポジトリから同期したプラグインには、Revert(巻き戻し)が使えません。直す場所はリポジトリで、デフォルトブランチに修正をpushして再同期します。修正が届くまでの間は、そのプラグインの提供状況をNot availableにすると、メンバーの画面と読み込みから外せます。マーケットプレイスごと削除するDelete sourceは、中のプラグインをすべて恒久的に消すので、この用途には向きません。
必須配布にしたプラグインが手元の端末でどう見えるかは、claude plugin disableが拒否される原因に整理しています。claude.ai側の仕組みの全体像はClaude.aiのプラグイン機能が入口になります。
よくある疑問
bin/を残したままClaude Codeだけに配れないか
Claude Codeはbin/をPATHに載せる側なので、組織同期を使わないルートならbin/付きのプラグインも扱えます。そのかわり、claude.aiとCoworkのメンバーには届きません。両方に配りたいなら、実行ファイルをscripts/に移し、呼び出しを${CLAUDE_PLUGIN_ROOT}経由にそろえる方向になります。
サブディレクトリのbin/も弾かれるか
拒否の対象は「トップレベルのbin/」と書かれています。scripts/bin/のような別階層の扱いは、挙げたページに記載がありません。scripts/直下に置くのが、記載どおりの形です。
まとめ
エラーの原因は、プラグイン直下のbin/というディレクトリ名そのものにあります。中身のスクリプトに問題があるわけではありません。実行ファイルをscripts/へ移し、スキル・フック・MCP設定からは${CLAUDE_PLUGIN_ROOT}で参照すれば、組織同期を通せます。
直す前にmarketplace.jsonの相対パスをたどってbin/を洗い出し、リポジトリの公開範囲とsourceの種類もあわせて見ておくと、同期のやり直しが1回で済みます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。