Claude Codeプラグインをzip配布する — --plugin-urlとsha256ピン留め
Claude Codeプラグインをzipアーカイブで配布する3段階(--plugin-dir・--plugin-url・archiveソース)とsha256ピン留め、認証付きダウンロードの設定手順です。
手元のzipを試したいのか、利用者に配りたいのか
Claude Codeのプラグインは、gitリポジトリやnpmパッケージだけでなくzipアーカイブでも配れます。gitもnpmも入っていない端末や、CIが吐き出したビルド成果物をそのまま配りたいときに向く経路です。ただし「試す」と「配る」ではコマンドが別物で、取り違えると「動作確認できたのに利用者の画面にプラグインが出ない」ことになります。
zipを扱う3つの経路
--plugin-dir
ローカルのディレクトリかzipをそのセッションだけ読み込みます。インストールにはなりません。v2.1.128でzipに対応しました。
--plugin-url
URLのzipをセッション開始時に取得して、そのセッションだけ読み込みます。v2.1.129で追加されたフラグで、これもインストールではありません。
archiveソース
marketplace.jsonのエントリにzipのURLを書きます。利用者の端末にインストールされるのはこの経路だけで、v2.1.224以降が必要です。
プラグインの構造そのもの(.claude-plugin/plugin.jsonやスキル・フックの配置)はClaude Codeプラグイン完全ガイドにまとまっています。初めて作る場合は、先にそちらで全体像をつかむと読みやすくなります。
ローカルのzipを一時ロードして試す
--plugin-dirはディレクトリのほか、プラグインを固めたzipもそのまま受け取ります。展開せずに起動確認できるので、配布用zipを作った直後の検証に向きます。
claude --plugin-dir ./my-plugin
claude --plugin-dir ./my-plugin.zip
claude --plugin-dir ./plugin-one --plugin-dir ./plugin-two.zipフラグは繰り返し指定でき、1つのフラグに渡せるパスは1つです。プラグインを複数入れたフォルダを渡す形も、v2.1.265以降で使えます。同名のインストール済みプラグインがあるときは、そのセッションでは--plugin-dir側が優先されます。アンインストールせずに変更を試せる反面、管理者のmanaged settingsでその名前が固定されているプラグインは上書きできません。managed settingsは--plugin-dirそのものを無効にすることもあります。
フラグを足せない起動経路では、環境変数のCLAUDE_CODE_PLUGIN_DIRSで同じ読み込みができます。編集のたびに再起動する必要もなく、/reload-pluginsで再起動なしにプラグインを反映する手順でスキル・フック・MCPサーバーの変更をまとめて読み込み直せます。
zipの中の並べ方を手元で確かめる
zipには、プラグインの中身を直接固めた形と、フォルダごと固めた形の2通りがあります。公式の説明は「プラグインのルートはzipの最上位か、1階層下にあってよい」です。同じプラグインを2通りに固めて、中身とダイジェストを見比べました(v2.1.287で確認、作業用の一時ディレクトリ内で実行)。
cd my-plugin && zip -qr ../flat.zip . && cd ..
zip -qr nested.zip my-plugin
unzip -l flat.zip
unzip -l nested.zip
shasum -a 256 flat.zip nested.zip# flat.zip(直接固めた形)
.claude-plugin/
.claude-plugin/plugin.json
commands/
commands/hello.md
# nested.zip(フォルダごと固めた形)
my-plugin/
my-plugin/.claude-plugin/
my-plugin/.claude-plugin/plugin.json
my-plugin/commands/
my-plugin/commands/hello.md
1bf559dd...af14c flat.zip
323bc3f9...c8169 nested.zipどちらの形も公式の許容範囲に収まります。ただし中身が同じでもダイジェストは別の値になります。後でsha256を書くときは、最終的にサーバーへ置くファイルの値を取る必要があります。
なお、claude plugin validateはzipを検証できません。flat.zipに流すと次のとおり、zipをJSONとして読もうとして落ちました。検証はzipに固める前のディレクトリに対して行います。
$ claude plugin validate flat.zip
✘ Found 1 error:
❯ json: Invalid JSON syntax: JSON Parse error: Unexpected identifier "PK"CI成果物のURLをその場で読み込む
CIがビルドしたzipをアーティファクトサーバーに置いているなら、--plugin-urlでURLから直接取得できます。
claude --plugin-url https://example.com/my-plugin.zip
claude --plugin-url "https://example.com/my-plugin.zip https://example.com/other.zip"Claude Codeはセッション開始時にアーカイブを取得し、そのセッション限りで読み込みます。複数読むときは、フラグを繰り返すか、1つの引数にスペース区切りで並べます。取得に失敗したときやzipの中身が不正なときは、プラグインなしで起動し、/pluginマネージャーのErrorsタブにロードエラーを残します。起動が止まることはありません。
marketplace.jsonで配布する手順
利用者にインストールしてもらうには、marketplace.jsonのプラグインエントリでarchiveソースを使います。gitもnpmも入っていない端末で入る点が、この方式の価値です。静的ファイルを配れるサーバー(S3・Artifactoryのgenericリポジトリ・nginxなど)であれば、置き場所は問いません。
リリースごとの作業
- 1
zipを作ってダイジェストを取る
サーバーに置くファイルそのものに対して
shasum -a 256 my-plugin-2.1.0.zipを実行します。PowerShellならGet-FileHash -Algorithm SHA256です。 - 2
marketplace.jsonのエントリを書く
sourceにarchive、url、sha256を並べます。URLはhttps://だけです。 - 3
配布前にvalidateする
claude plugin validate ./my-marketplaceで、URLの形式やダイジェストの桁数の誤りを拾います。 - 4
自分の端末でインストールを試す
claude plugin install my-plugin@your-marketplaceで、実際に取得とインストールが通ることを確かめてから案内します。
エントリの書き方は次のとおりです。sha256の省略もできますが、扱いが変わります(次の節)。
{
"name": "my-plugin",
"source": {
"source": "archive",
"url": "https://artifacts.example.com/claude-plugins/my-plugin-2.1.0.zip",
"sha256": "6bfa50e3d2e00c052b46abe51fff89346ac803e45771f76dcf6df1ab74cca5e1"
}
}validateが何を拾うかは、http://のURLと桁数の足りないsha256を書いたエントリで確かめました(v2.1.287)。ネットワークに出る前の入力ミスは、この段階で止まります。
$ claude plugin validate mk
✘ Found 2 errors:
❯ plugins.1.source.url: Archive URLs must use https:// and must not point
at a loopback, link-local, or cloud-metadata host
❯ plugins.1.source.sha256: Must be a 64-character hex SHA-256 digestarchiveソースを使えるのはClaude Code v2.1.224以降です。それより古い本体で該当プラグインを入れると、次のメッセージで止まります。
This plugin uses a source type your Claude Code version does not support
Update Claude Code and try again.社内配布では、本体のバージョンを先に周知しておく必要があります。
Node.jsの依存を同梱する
zipにNode.jsの依存を含めたいときは、プラグインのルート直下にpackage.jsonとロックファイルの両方を置きます。ロックファイルでパッケージマネージャーが決まり、複数あるときはbun.lock、npm-shrinkwrap.json、package-lock.jsonの順に最初の一致が使われます。次の場合は依存のインストールが行われません。
- Bunのバイナリ形式
bun.lockbだけを同梱した(検証できないため。テキストのbun.lockか、npmのロックファイルにする) - npmのロックファイルの
lockfileVersionが2か3でない package.jsonにnpmのoverrides(npmのロックファイルからの導入が止まる)、またはBunのpatchedDependencies(bun.lockからの導入が止まる)があるyarn.lockかpnpm-lock.yamlだけを同梱した(npmのロックファイルに置き換える)- git・GitHub・フォルダ・ワークスペース・リンクの依存がある(registryのパッケージを厳密なバージョンで固定した依存だけが対象)
npmソースで配るときは、npm publishがpackage-lock.jsonを除外するためnpm-shrinkwrap.jsonが要ります。archiveソースはzipをそのまま送るので、この制約は受けません。
ダウンロードの上限に収める
サーバーとzipの大きさには上限があり、超えるとインストールが失敗します。
| 対象 | 上限 |
|---|---|
| zipのダウンロード | 上限256MiB、応答まで120秒、リダイレクトは5回まで |
| 展開後のエントリ数 | 上限10万(ファイルとディレクトリの合計) |
| 展開後の1ファイル | 上限512MiB |
| 展開後の合計 | 上限1GiB |
| 圧縮率 | 上限展開後がzipの50倍まで |
リダイレクトは各回ともhttps://が条件で、ループバックアドレス、リンクローカルアドレス、クラウドのメタデータエンドポイントへの転送は拒否されます。ビルド成果物にnode_modulesや学習用データを丸ごと入れると、展開後の合計かエントリ数で落ちやすくなります。
sha256ピンはバージョン管理と一体で考える
sha256は省略できます。versionを明示していない場合、省略するとホストしたzipのバイト列が変わるたびに、利用者側では別バージョン扱いです。ピンを置くと、ダウンロードしたファイルがダイジェストと一致しないときにClaude Codeはインストールを拒否します。公式のトラブルシュートに載っているエラーは次の形です。
Plugin archive integrity check failed for https://artifacts.example.com/claude-plugins/my-plugin.zip:
expected sha256 6bfa50e3..., got ac52220c.... The archive was not installed.改ざんだけでなく、zipを作り直してダイジェストを更新し忘れたときも同じ表示になります。公開側の対処は、URLが返す実ファイルのダイジェストを取り直してエントリに書くことです。利用者側は/plugin marketplace update <name>でカタログを更新してから再試行します。詳しくは「archive integrity check」の対処にあります。
plugin.jsonにもマーケットプレイスのエントリにもversionがないとき、バージョンはsha256のピン(ピンがなければダウンロードしたファイルのダイジェスト)の先頭12文字になります。明示的なversionがあるとそちらが優先されるので、zipとダイジェストを差し替えたらversionも上げてください。上げ忘れると、利用者はキャッシュ済みの古いコピーを使い続けます。
| バージョンの決め方 | 設定 | 更新のタイミング |
|---|---|---|
| 明示バージョン | 設定plugin.jsonなどにversionを設定 | 更新のタイミングフィールドを変えたときだけ |
| ダイジェストバージョン(ピンあり) | 設定archiveソース + sha256 | 更新のタイミングsha256の値を変えたとき |
| ダイジェストバージョン(ピンなし) | 設定archiveソース + sha256省略 | 更新のタイミングホストしたzipのバイト列が変わるたび |
継続的に配る社内プラグインにはダイジェストバージョンが、リリースの節目を明示したい公開プラグインには明示バージョンが合います。
プライベートなzipを認証付きで配る
アクセスに認証が要るレジストリなら、ダウンロード時に送るHTTPヘッダーを設定できます。headersは、マーケットプレイスを登録したURLソース(extraKnownMarketplacesのエントリなど)に置くのが基本です。v2.1.238以降は、sourceと並べてプラグインのエントリにも置けます。トークンが短命なら、headersの代わりにheadersHelperへコマンドを書きます。コマンドが出力したJSONオブジェクトがヘッダーになります。
{
"name": "my-plugin",
"description": "Formatting commands for internal services",
"strict": false,
"commands": "./commands",
"source": {
"source": "archive",
"url": "https://registry.example.com/plugins/my-plugin-2.1.0.zip"
},
"headersHelper": "/opt/bin/mint-registry-token.sh"
}headersHelperを置くエントリには"strict": falseが必須です。利用者がコマンドを許可する前に、エントリだけでプラグインの中身を確認できるようにするためです。strictを書かずにheadersHelperを置いたエントリをvalidateにかけると、次のエラーになりました(v2.1.287)。
✘ Found 1 error:
❯ plugins[0].headersHelper: Plugin "my-plugin" sets headersHelper but is not
"strict": false. An entry with headersHelper must inline its full manifest ..."strict": falseを足してもまだ警告が1件残り、sha256を書くと消えます。ヘルパーでダウンロードするなら、ピンも置いておくのが前提の設計です。
ヘッダーを置く場所で、反映範囲が変わります。
- マーケットプレイスのURLソース: 同じオリジン(スキーム・ホスト・ポートが同じ)へのアーカイブダウンロードすべてに付く。ヘルパーは取得のたびに走り、出力は最長60秒再利用される
- プラグインのエントリ: そのプラグインのダウンロードだけに付く。ヘルパーは、利用者が単体でインストールか更新をして、コマンドを許可したときだけ走る
同名のヘッダーが両方にあるときは、エントリ側の値が送られます。ヘルパーの条件にも制約があり、コマンドは印字可能なASCIIで500文字以内、4つ以上連続するスペースは含められません。10秒以内にJSONを出力して終了コード0で終える必要があります。作業ディレクトリは~/.claude(またはCLAUDE_CONFIG_DIR)なので、パスは絶対パスで書きます。
ここには見落としやすい挙動が2つあります。1つは、環境変数の扱いです。marketplace.jsonのエントリやプロジェクトの.claude/settings.jsonに書いたヘルパーからは、名前が認証情報らしい変数(MY_REGISTRY_TOKENなど)がすべて取り除かれます。トークンはファイルか認証情報ストアから読ませます。もう1つは、リダイレクトでオリジンが変わると、ヘッダーもヘルパーの出力も転送先に送られない点です。署名付きURLへ飛ばす構成だと、飛び先には認証が付かないので別途考える必要があります。非対話のシェルでエントリのコマンドを許可するには、claude plugin installに--yesを付けます。
配布設計に関わる制約がさらに3つあります。
- エントリのヘルパーは、単体のインストールか更新のときだけ走り、アーカイブも取得されます。一括インストール、依存としてのインストール、バックグラウンドの自動更新では、コマンドも走らずダウンロードもされません。他のプラグインの依存にしたいプラグインには、
headersHelperを付けない設計が必要です - managed settingsで
disableCommandPluginSourcesをtrueにすると、ヘルパーのコマンドは遮断されます。allowManagedHooksOnlyも、disableCommandPluginSourcesを明示的にfalseにしていない限り同じく遮断します。どちらの場合も、managed settings自身が宣言したマーケットプレイスのコマンドは実行されます - エントリに書いた
Host・Cookie・X-Forwarded-*のようなリクエスト経路やクライアント識別に関わるヘッダー名は、破棄されます
よくあるつまずきと回避策
| 症状 | 原因 | 対処 |
|---|---|---|
--plugin-urlで試したプラグインが利用者側に出ない | 原因--plugin-urlはセッション限りの読み込み | 対処配布はarchiveソースで行う |
| zipを差し替えたのに利用者側が更新されない | 原因明示versionの上げ忘れ(ピンだけ変えてversionが固定のまま) | 対処ピン運用ならピンの変更だけで足りる。明示バージョン運用ならversionも上げる |
| 「integrity check failed」でインストールが止まる | 原因ダウンロードしたファイルがsha256と一致しない | 対処zipの再ビルド後はダイジェストを取り直す |
| 古いClaude Codeでインストールが止まる | 原因v2.1.223以前はarchiveソースを扱えない | 対処本体をv2.1.224以降に更新する |
| 非公開サーバーのzipが取れない | 原因認証ヘッダー未設定、URLがhttp://やループバック | 対処headersかheadersHelperを設定する。URLはhttps://にする |
プラグインのキャッシュがどこに展開され、更新差分をどう検知しているかはClaude Codeプラグインのキャッシュとファイル解決の仕組みで扱っています。git commit SHA・npm・archiveダイジェストの優先順位はClaude Codeプラグインのリリースチャネルとバージョン解決を参照してください。
まとめ
試すだけなら--plugin-dirか--plugin-url、利用者の端末に入れるならarchiveソースとv2.1.224以降の本体、という組み合わせで決まります。配布前のvalidateと自分の端末でのインストール確認までが、利用者に案内する前の最低ラインです。