Claude Media
claude plugin組織同期でbin/ディレクトリが弾かれる原因と直し方

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. 1

    実行ファイルを移す

    bin/の中身をscripts/へ移します。ディレクトリ名はscripts/でなくても構いません。

  2. 2

    呼び出し側を書き換える

    素のコマンド名で呼んでいた箇所を、${CLAUDE_PLUGIN_ROOT}/scripts/<name>に変えます。

  3. 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}の形で書くと、読み込み時にパスへ置き換えられます。

前と後

スキルからの呼び出しを書き換える

BEFORE

bin/を前提にした書き方

SKILL.mdの本文:

デプロイ前に deploy-tool --check を実行してください。
AFTER

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"
    done

stringsで絞っているので、対象は./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回で済みます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/plugins/host-marketplace
  2. claude.com/docs/plugins/org-sync
  3. code.claude.com/docs/en/plugins-reference
  4. claude.com/docs/plugins/org-rollout
この記事を共有:XはてブLinkedIn