Claude CodeプラグインのheadersHelperで認証付きアーカイブを配る
プライベートレジストリのzipを配るときのheadersHelperの書き方と制約を示します。strict: falseの必須条件、10秒・JSON・500字の制約、ヘッダーが捨てられる条件、利用者の承認までを扱います。
社内レジストリに置いたプラグインのzipを、トークン付きでしか取れない形のまま配りたい。この用途に使うのが、marketplace.jsonのプラグインエントリに書く headersHelper です。Claude Code v2.1.238以降で使え、ダウンロードの直前にコマンドを走らせて認証ヘッダーを作ります。
先に結論を挙げます。エントリには "strict": false が必須です。コマンドは10秒以内に、値がすべて文字列のJSONオブジェクトを標準出力へ出して終了コード0で終わる必要があります。そして利用者は、インストールや更新のたびにそのコマンドを承認します。
headersHelperは何を解決するのか
archive ソース(HTTPS経由のzip)は、git もGitホストのアカウントも要りません。ネットワークで取れさえすれば配れます。ただしレジストリが認証を求めるなら、ダウンロード時にHTTPヘッダーを足す必要があります。
固定のトークンなら headers に直接書けます。有効期限の短いトークンをレジストリが都度発行する構成では、書き置きの値がすぐ古くなります。そこで headersHelper に「トークンを出力するコマンド」を指定し、毎回新しい値を使わせます。
ヘッダーを置ける場所は2つあり、動き方が違います。
| 置き場所 | ヘッダーが付く通信 | コマンドが走るタイミング |
|---|---|---|
マーケットプレイスの url ソース | ヘッダーが付く通信同じスキーム・ホスト・ポート(オリジン)のアーカイブ取得 | コマンドが走るタイミングmarketplace.json を取るたび、およびそのオリジンへのアーカイブ取得のたび。1回の出力は最長60秒使い回す |
| プラグインのエントリ | ヘッダーが付く通信そのエントリ1件のダウンロードだけ | コマンドが走るタイミング利用者がそのプラグインを単独でインストール・更新し、コマンドを承認したとき |
両方に同名のヘッダーがあるときは、エントリ側の値が送られます。同じ場所の中では、コマンドが出力したヘッダーが headers の同名ヘッダーを上書きします。エントリ側のヘッダーはv2.1.238以降で使えます。本記事は、利用者ごとの承認が絡むエントリ側を中心に書きます。
エントリにheadersHelperを書く
source の隣に headersHelper を置きます。例は公式ドキュメントの形に沿ったものです。
{
"name": "my-plugin",
"description": "Formatting commands for internal services",
"strict": false,
"source": {
"source": "archive",
"url": "https://registry.example.com/plugins/my-plugin-2.1.0.zip"
},
"headersHelper": "/opt/bin/mint-registry-token.sh"
}strict を false にしないと、claude plugin validate がエラーにします。メッセージは Plugin "x" sets headersHelper but is not "strict": false で、対象は archive エントリの headersHelper です。strict の挙動そのものはマーケットプレイスのstrict modeとvalidateの使い方にまとめています。
validateには、同じ headersHelper まわりの警告もあります。
sourceがarchiveでないエントリにheadersやheadersHelperを書くと「効果がない」という警告が出ますheadersHelperで取るアーカイブにsha256がないと、ピン留めがないという警告が出ます
認証付きで配るなら、source に sha256 も足しておくと改ざんや差し替えを検出できます。sha256 は64桁の16進数で、設定すると一致しないダウンロードを拒否します。
設定を書いたら、自分の端末で次を実行して動作を見ます。
claude plugin install my-plugin@your-marketplaceClaude CodeがコマンドとアーカイブURLを表示し、承認するとzipをダウンロードします。
コマンドの書き方と制約
コマンドは、マーケットプレイスの url ソースに書く場合もエントリに書く場合も、同じ条件を満たす必要があります。
- コマンド文字列: 500文字以内の印字可能なASCII。スペースを4つ以上続けない
- 出力: ヘッダー名と文字列値のJSONオブジェクトを標準出力に1つ出し、10秒以内に終了コード0で終わる
- 実行環境:
sh(Windowsではcmd.exe)経由で、作業ディレクトリは設定ディレクトリ(~/.claudeかCLAUDE_CONFIG_DIR)
作業ディレクトリが利用者のプロジェクトではない点が落とし穴です。相対パスは設定ディレクトリ基準で解決されるので、絶対パスか PATH 上のコマンドを指定します。
認証情報は環境変数で渡せない
marketplace.json のエントリ、またはプロジェクトの .claude/settings.json・.claude/settings.local.json に書いたコマンドでは、認証情報らしい名前の環境変数が取り除かれます。ANTHROPIC_API_KEY も MY_REGISTRY_TOKEN も対象です。この除去は、ユーザー設定・--settings ファイル・管理設定に書いたコマンドには働きません。
カタログ側のエントリでは、資格情報をファイルか認証情報ストアから読ませます。たとえば次のようなスクリプトが考えられます(説明用の例で、レジストリのAPIは各自の環境に合わせて読み替えてください)。
#!/bin/sh
# 資格情報はファイルから読み、短命トークンの取得は各自のレジストリ仕様に従う
token="$(cat "$HOME/.config/my-registry/token")"
printf '{"Authorization": "Bearer %s"}\n' "$token"出力は {"Authorization": "Bearer eyJhbGciOiJSUzI1NiJ9"} のような、文字列値だけのオブジェクトにします。数値や入れ子の値を含めたり、JSON以外を出力したりすると失敗扱いです。
コマンドが受け取る環境変数
Claude Codeは、呼び出しの種類に応じて次の変数をコマンドに渡します。
| 変数 | 渡される場面 |
|---|---|
CLAUDE_CODE_MARKETPLACE_URL / CLAUDE_CODE_MARKETPLACE_NAME | 渡される場面url ソースのコマンド |
CLAUDE_CODE_PLUGIN_NAME / CLAUDE_CODE_PLUGIN_ARCHIVE_URL | 渡される場面エントリのコマンド |
ユーザーがURLでマーケットプレイスを追加した最初の取得では、取得結果がマーケットプレイス名を決めるため、CLAUDE_CODE_MARKETPLACE_NAME は未設定です。
ヘッダーが捨てられる条件
次のどれかに当たると、コマンドが走らない、またはヘッダーが捨てられます。配信後に「認証が通らない」と言われたら、まずこの一覧で切り分けます。
認証ヘッダーが届かないときの切り分け
- QアーカイブURLはhttps://で始まっていますか?答えがいいえ答えがはい
https以外では
urlソースのコマンドが走りません。httpsに変えます。 - Qダウンロードが別オリジンへリダイレクトされていませんか?答えがされている答えがはい
リダイレクト先のリクエストには、
urlソースとエントリのどちらのヘッダーも付きません。リダイレクトしない配信元に置きます。 - QHost・Cookie・X-Forwarded-* などのヘッダーを出していませんか?答えが出している答えがはい
エントリの
headersとコマンド出力から、ルーティングや送信元識別のヘッダーは削除されます。Authorizationのような認証用の名前は残ります。 - Qコマンドは10秒以内に終了コード0で、文字列値のJSONだけを出していますか?答えがいいえ答えがはい
非ゼロ終了・10秒超過・JSON以外の出力では、そのフェッチやダウンロード自体が行われません。
それでも届かない場合
管理設定の disableCommandPluginSources と、--add-dir の設定ファイルを確認します。詳細は次の節にあります。
その他の遮断条件が2つあります。
--add-dirで追加したディレクトリの設定: そこに書かれたheadersHelperは無視されます。送られるのはそのファイルの固定のheadersだけです- 管理設定:
disableCommandPluginSourcesをtrueにするとheadersHelperのコマンドがブロックされます。allowManagedHooksOnlyも、disableCommandPluginSourcesが明示的にfalseでない限りブロックします。管理設定自身が宣言したマーケットプレイスのコマンドは、どちらの場合も実行されます
管理者が組織向けに運用するなら、この2つのキーの組み合わせが配信の成否を左右します。マーケットプレイスの必須化と自動更新の組み立てはClaude Codeプラグインマーケットプレイスの必須化と自動更新設定が参考になります。
利用者がコマンドを承認する流れ
エントリのコマンドは、利用者が単独のプラグインとしてインストール・更新するたびに承認する形です。Claude CodeがコマンドとアーカイブURLを表示し、利用者が承認して初めて実行します。
承認の窓口は3つあります。
承認できる場所
ターミナルのセッション
/pluginで開くプラグイン個別の画面から行います。シェル
claude plugin installまたはclaude plugin updateで行います。非対話のシェルでは--yesで承認できます。VS Code拡張機能
Manage pluginsダイアログから行います。拡張機能のバージョンは2.1.290以降が必要です。
承認の対象は、表示したコマンドと表示したアーカイブURLに固定されます。間にエントリのコマンドやURLが変わると、インストールも更新も拒否されます。クエリ文字列だけの変更は同一扱いで、VS Code拡張機能と --accept-command を使うときは例外です。
CIのような非対話環境では、直前の --json 実行が出した shownCommand の sha256 を --accept-command に渡す方法もあります。-y とは併用できません。TTYがなく -y も --accept-command も付けない場合、インストールは拒否され、終了コードは1です。承認を断ったときは Aborted — the command was not run. と出て、同じく終了コード1になります。
承認の画面が出ない操作は拒否される
次の操作では、Claude Codeはコマンドを実行せず、アーカイブもダウンロードしません。
- 複数プラグインの一括インストール、プラグインの提案、他プラグインの依存としてのインストール: コマンドを持つプラグインだけが拒否され、
/pluginのそのプラグイン個別の画面を案内されます。ほかのプラグインは入ります。依存元のプラグインは、拒否されたプラグインを単独で入れるまでインストールに失敗します - バックグラウンドの自動更新、およびアーカイブをまだ取得していないプラグインのセッション開始時:
/pluginのErrorsタブに載り、利用者が自分でインストールか更新をするよう促されます
「自動更新で勝手に新版が降りてくる」前提の運用はできません。コマンド付きエントリは、更新のたびに誰かが承認する運用になります。バージョンの上げ方や名前変更の移行はClaude Codeのマーケットプレイスでrenamesを使いプラグイン名を移行すると合わせて設計します。
urlソースに置く場合との使い分け
url ソースの headersHelper は、カタログ側(公開される marketplace.json)ではなく設定ファイルに宣言します。そのため利用者の承認は毎回は求められません。代わりに、宣言した設定ファイルの種類がコマンドを走らせる条件を決めます。
| 設定ファイル | コマンドが走る条件 |
|---|---|
ユーザー設定・--settings ファイル・端末上の管理設定ファイル | コマンドが走る条件確認なし。バックグラウンドのマーケットプレイス更新でも走る |
プロジェクトの .claude/settings.json・.claude/settings.local.json | コマンドが走る条件そのフォルダのワークスペース信頼ダイアログを承認したときだけ |
| サーバー管理設定 | コマンドが走る条件対話セッションで、配信された設定をセキュリティ承認ダイアログで承認したときだけ |
マーケットプレイス全体を同じレジストリ配下のアーカイブで構成し、利用者に毎回の承認をさせたくないなら、url ソース側に置く選択肢があります。逆に、プラグインごとに認証先が違う、または1つのプラグインだけ認証が要る構成なら、エントリ側が向きます。ソース型の全体比較はClaude Codeプラグインソースの使い分けで扱っています。
つまずいたときの切り分け
- validateで
not "strict": falseと出る:strictをfalseにします。カタログ内のエントリは、必ずこの設定が必要です - インストールで
Aborted — the command was not run.: TTYがないか、承認を断っています。シェルなら--yesか--accept-commandを使います - 一括インストールでだけ入らない: コマンド付きプラグインは一括では拒否されます。
/pluginのプラグイン個別画面から単独で入れます - 名前の似たMCP側のエラーと混同する: MCPサーバーの
headersHelperは別の機能です。Server rejected系のエラーはheadersHelperのServer rejectedエラーの原因で扱っています
関連するリリース
この機能が入ったのはClaude Code v2.1.238です。同バージョンの変更一覧はClaude Code v2.1.238のリリースノートにまとまっています。
まとめ
headersHelper は、有効期限の短い認証付きアーカイブを配るための仕組みです。カタログのエントリに書く場合は "strict": false、10秒・文字列値JSON・500文字以内のコマンド、認証情報はファイルかストアから読む、が最小限の条件になります。その代わり利用者は毎回承認し、一括インストールと自動更新では動きません。承認の手間が許容できない構成なら、url ソース側への配置か、headers の固定値を検討する余地があります。