TL;DR
- Claude Code 2.1.238 で、プラグインマーケットプレイスに
headersHelperが入りました。短命トークンなどの HTTP ヘッダーをコマンドで生成し、カタログと同一オリジンのアーカイブ取得に添える仕組みです。 - 認証必須のカタログを立てて実際に試したところ、
claude -pのような 非対話セッションでは helper が一度も実行されず、Authorizationが空のままフェッチされて 401 になりました。警告もエラーも表示されません。 - 同じ設定で
headersHelperを静的なheadersに差し替えると、Authorizationと独自ヘッダーの両方がサーバーに届き、マーケットプレイスの登録まで通りました。 - CI やスケジュール実行のように人が画面を見ていない経路で認証つきカタログを配るなら、現時点では静的ヘッダーか別の配信方式を選ぶのが安全です。
はじめに
社内向けのプラグインを配りたいとき、GitHub の公開リポジトリに置けない事情はよくあります。認証つきの静的ファイルサーバーや成果物リポジトリにカタログを置き、トークンを持つ人だけが取得できる形にしたい、という要求です。
Claude Code 2.1.238 の CHANGELOG には、まさにその用途に見える一行が載っています。
Plugin marketplaces:
headersHelperon a url marketplace or a catalog entry runs a command that mints HTTP headers (e.g. a short-lived token) for catalog and same-origin archive fetches
(出典: anthropics/claude-code CHANGELOG)
対象読者は、Claude Code のプラグインを社内やチーム内に配布したいエンジニアです。認証つきカタログを立てる前に、どの経路なら実際にヘッダーが付くのかを確かめました。結論から言うと、付く経路と付かない経路がはっきり分かれます。
なお 2026-08-21 時点で、公式ドキュメントのマーケットプレイス解説ページに headersHelper の記述は見当たりませんでした。挙動は CHANGELOG と実機で確かめるしかない状態です。
headersHelper の設定はどこに書くのか
claude plugin marketplace add --help を確認すると、オプションは --scope と --sparse の 2 つだけで、ヘッダーを渡す口はありません。
Usage: claude plugin marketplace add [options] <source>
Add a marketplace from a URL, path, or GitHub repo
Options:
-h, --help Display help for command
--scope <scope> Where to declare the marketplace: user (default),
project, or local
--sparse <paths...> Limit checkout to specific directories via git
sparse-checkout (for monorepos). Example: --sparse
.claude-plugin plugins
設定は settings.json の extraKnownMarketplaces に書きます。CLI バンドルに埋め込まれた設定スキーマの説明文では、url ソースは次の 3 つのフィールドを取ります。
| フィールド | 説明(CLI 内蔵スキーマの記述より) |
|---|---|
url |
marketplace.json への直接 URL |
headers |
カスタム HTTP ヘッダー(認証用途など) |
headersHelper |
HTTP ヘッダーの JSON オブジェクトを標準出力に出すコマンド。出力は headers を上書きし、同一オリジンのアーカイブ取得にも継承される |
headersHelper については、実行位置に関する注記も付いています。セッションの作業ディレクトリではなく Claude の設定ホームから実行されるため、PATH で解決できる素のコマンド名か絶対パスを渡すこと、そしてマーケットプレイスの更新時に再実行されること、の 2 点です。相対パスのスクリプトを書くと外れます。
headersHelper の値そのものにも、専用のバリデータで制約が掛かっています。印字可能な ASCII のみで、4 文字以上連続する空白を含められず、長さの上限は「インストール同意の UI が表示できる長さ」と説明されています。この一言が後の挙動を理解する鍵になりました。
検証環境
検証には Claude Code 2.1.238 を使いました。
$ claude --version
2.1.238 (Claude Code)
カタログ配信側として、Authorization: Bearer sekret-token-abc123 を要求する Node.js の HTTP サーバーを立てました。受け取ったヘッダーを毎回ログファイルへ追記し、誰が何を送ってきたかを後から確認できるようにしてあります。
const auth = req.headers['authorization'] || '';
fs.appendFileSync(LOG, JSON.stringify({
ts: new Date().toISOString(),
url: req.url,
authorization: auth,
'x-catalog-env': req.headers['x-catalog-env'] || ''
}) + '\n');
if (auth !== `Bearer ${TOKEN}`) {
res.writeHead(401, { 'content-type': 'application/json' });
res.end(JSON.stringify({ error: 'unauthorized' }));
return;
}
サーバー単体の挙動は curl で確認済みです。ヘッダーなしは 401、正しいトークン付きは 200 でカタログ JSON が返ります。
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8791/marketplace.json
401
$ curl -s -H 'Authorization: Bearer sekret-token-abc123' http://127.0.0.1:8791/marketplace.json
{"name":"internal-catalog","owner":{"name":"Internal Platform Team"},"plugins":[...]}
既存の設定を壊さないよう、CLAUDE_CONFIG_DIR で設定ディレクトリを隔離し、作業ディレクトリも一時領域に置いています。
実験 1: ヘッダーなしで add すると当然 401
まず素直に URL を渡します。
$ claude plugin marketplace add http://127.0.0.1:8791/marketplace.json
× Failed to add marketplace: HTTP 401 error while downloading marketplace from
http://127.0.0.1:8791/marketplace.json. The marketplace file may not exist at this URL.
Technical details: Request failed with status code 401
ここは想定どおりです。メッセージの後半が「この URL にファイルが無いのかもしれません」と誘導してくる点だけ気になりますが、Technical details に 401 が出るので原因の切り分けはできます。
実験 2: headersHelper を設定しても helper が動かない
ヘッダーを生成するスクリプトを絶対パスで用意しました。実運用では IdP から短命トークンを取る想定の位置に、固定値を置いています。実行されたかどうかを見たいので、自分の実行ログも残します。
#!/usr/bin/env bash
echo "[mint] pwd=$(pwd)" >> /tmp/hh/srv/helper.log
printf '{"Authorization":"Bearer sekret-token-abc123","X-Catalog-Env":"staging"}\n'
設定は extraKnownMarketplaces に書きます。
{
"extraKnownMarketplaces": {
"internal-catalog": {
"source": {
"source": "url",
"url": "http://127.0.0.1:8791/marketplace.json",
"headersHelper": "/tmp/hh/srv/mint-headers.sh"
}
}
}
}
この状態で claude plugin marketplace list を実行しても No marketplaces configured のままでした。extraKnownMarketplaces はセッションの起動時に読まれるため、セッションを 1 度起こす必要があります。
$ claude -p "reply with exactly: OK" --max-turns 1
OK
セッションは正常に応答しましたが、サーバー側のリクエストログは空のままでした。作業ディレクトリの信頼(trust)が未承認だったためです。設定ディレクトリの .claude.json に projects["/tmp/hh/work"].hasTrustDialogAccepted = true を書いてから、もう一度セッションを起こします。
今度はフェッチが走りました。ただし中身が問題です。
{"ts":"2026-08-21T03:12:46.497Z","url":"/marketplace.json","authorization":"","x-catalog-env":""}
authorization が空文字です。ヘルパースクリプト側のログファイルは生成すらされていませんでした。つまり helper は 1 度も実行されていません。結果としてサーバーは 401 を返し、マーケットプレイスは登録されないまま終わります。
そして、この失敗はセッションの出力に一切現れませんでした。claude -p の出力は OK の 1 行だけで、警告もエラーもありません。プロジェクトスコープの .claude/settings.json から、ユーザースコープの $CLAUDE_CONFIG_DIR/settings.json へ設定を移しても結果は同じでした。
実験 3: 静的 headers に差し替えると通る
同じ URL・同じマーケットプレイス名のまま、headersHelper を静的な headers に置き換えます。
{
"extraKnownMarketplaces": {
"internal-catalog": {
"source": {
"source": "url",
"url": "http://127.0.0.1:8791/marketplace.json",
"headers": {
"Authorization": "Bearer sekret-token-abc123",
"X-Catalog-Env": "static"
}
}
}
}
}
セッションを起こすと、サーバーには両方のヘッダーが届きました。
{"ts":"2026-08-21T03:14:03.992Z","url":"/marketplace.json","authorization":"Bearer sekret-token-abc123","x-catalog-env":"static"}
マーケットプレイスの登録も通ります。
$ claude plugin marketplace list
Configured marketplaces:
> internal-catalog
Source: URL (http://127.0.0.1:8791/marketplace.json)
Authorization 以外の独自ヘッダー(ここでは X-Catalog-Env)もそのまま透過します。プロキシやアーティファクトリポジトリ側でヘッダーによる振り分けをしている環境でも使えます。
実測のまとめ
| 設定 | helper 実行 | サーバーに届いた Authorization | 登録結果 |
|---|---|---|---|
設定なしで marketplace add
|
該当なし | なし | 401 で失敗(エラー表示あり) |
headersHelper(プロジェクトスコープ) |
されない | 空 | 401・登録されない(表示なし) |
headersHelper(ユーザースコープ) |
されない | 空 | 401・登録されない(表示なし) |
headers(静的) |
該当なし | 届く | 登録成功 |
筆者の所見
実際に動かしてまず引っかかったのは、失敗が静かすぎることでした。marketplace add で明示的に 401 を踏んだときはメッセージが出ますが、extraKnownMarketplaces 経由の取得はセッション起動の裏側で走るため、失敗しても表面に出てきません。セッションは普通に応答を返し、プラグインだけが存在しない状態になります。「設定したのに /plugin に何も出てこない」と感じたら、まずカタログ側のアクセスログを見るのが早いと思います。
helper が実行されなかった理由は断定できませんが、CLI に埋め込まれた説明文がヒントになります。headersHelper の値には「インストール同意の UI が表示できる長さ」という制約があり、カタログエントリ側の helper については CHANGELOG が「コマンドが表示された後に実行される」と明記しています。
A catalog entry's
headersHelperruns only when you install or update that plugin, after its command is shown;claude plugin install/updateask[y/N](or pass-y)
(出典: anthropics/claude-code CHANGELOG)
コマンドを表示してから [y/N] で聞く、という設計です。聞く相手がいない非対話セッションで実行されないのは筋が通ります。エラーではなく仕様の帰結と考えるのが自然です。とはいえ、静かにスキップされて 401 になる挙動は、設定した側からは区別が付きません。
もう 1 つ、信頼(trust)の承認が前提になっている点も実務では効いてきます。承認前は取得そのものが走らず、承認後に初めてリクエストが飛びました。新しく clone したリポジトリで初回だけプラグインが入らない、という現象が起きたときは、この順序を疑うと切り分けが早くなります。
実務でどう使うか
配布経路を人が操作するかどうかで、選ぶ手段が変わります。
-
開発者が手元で使う:
headersHelperが本来の用途です。短命トークンを都度発行でき、settings.jsonに長命の秘密を書かずに済みます。同意の表示を挟む前提なので、対話セッションでの利用に向きます。 -
CI・スケジュール実行・コンテナの初期化: 現時点では静的
headersを使い、値そのものは環境変数からsettings.jsonを生成する形にするのが確実です。生成した設定ファイルを成果物として残さない運用と組み合わせます。 -
組織で配布を統制する: マネージド設定側には
disableCommandPluginSourcesとallowManagedHooksOnlyがあり、マーケットプレイスが宣言したコマンドの実行を止められます。統制をかける環境では、そもそも helper が動かない前提で設計しておくほうが安全です。
なお、カタログエントリ(プラグイン単位)に headersHelper を書く場合は strict: false が必要で、エントリ内にマニフェストをインラインで展開する必要があります。何を配るかをコマンド実行前にレビューできるようにするための制約です。この経路は今回は検証していません。
おわりに
headersHelper は、社内カタログの認証を「設定ファイルに秘密を置かない」形で解く機能です。ただし今回試した範囲では、非対話セッションでは helper が実行されず、認証なしのフェッチが静かに 401 になりました。人が見ている場面と、見ていない場面で、同じ設定が同じようには動きません。
自動実行の経路に組み込む前に、カタログ側のアクセスログで Authorization が実際に届いているかを確認することをおすすめします。届いていないことに気付かないまま「プラグインが入らない」を追いかけると、設定ではなく無関係な場所を疑うことになります。