Claude Media
Claude Codeのplugin-dirで変更を反映する2つの環境変数

Claude Codeのplugin-dirで変更を反映する2つの環境変数

--plugin-dirで読んだmodは対話では保存すると再読み込みされます。CLAUDE_CODE_PLUGIN_DIR_WATCHで非対話にも広げる条件と、BACKGROUND_PLUGIN_REFRESHの代償を解説します。

--plugin-dirで読み込んだmodは、対話セッションならファイルを保存した時点で自動的に再読み込みされます。claude -pのような非対話セッションでは、この再読み込みは既定でオフです。そこを切り替えるのがCLAUDE_CODE_PLUGIN_DIR_WATCHで、v2.1.287以降で使えます。

もう一つのCLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESHは別の話です。こちらは「ファイルを編集したとき」ではなく「バックグラウンドのプラグイン導入が終わったとき」に、非対話セッションのプラグイン状態を更新します。名前が似ていても、動くきっかけが違います。

この記事では、2つの変数の既定値と値の意味、再読み込みでmodの中に起こること、有効にする前に見ておく代償を順に扱います。

2つの変数は何のきっかけで動くか

くらべる

変更を反映する2つの環境変数

mod のファイル保存で動く

CLAUDE_CODE_PLUGIN_DIR_WATCH

--plugin-dirで読んだmodのファイルが変わると、そのmodを再読み込みします。対話セッションでは既定でオン。非対話セッションで使うには1を設定します。

バックグラウンド導入の完了で動く

CLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESH

非対話モードで、バックグラウンドのプラグイン導入が終わったあとのターン境界で、プラグイン状態を更新します。既定ではオフです。

前者は開発中のmodを書き換える人向け、後者はclaude -pをCIやスクリプトから回す人向けの設定です。

CLAUDE_CODE_PLUGIN_DIR_WATCHの値と既定値

この変数は、modのファイルが変わったときにClaude Codeがそのmodを再読み込みするかを決めます。対象は--plugin-dirでディレクトリから読んだmodです。値ごとの結果は次のとおりです。

設定対話セッション非対話セッション(-pなど)
未設定対話セッション再読み込みする非対話セッション(-pなど)しない
1対話セッション再読み込みする非対話セッション(-pなど)再読み込みする
0対話セッションしない非対話セッション(-pなど)しない

1は、長く動く非対話セッションで--plugin-dirのmodを保存時に再読み込みさせる設定です。0は全セッションで再読み込みを止めます。対話セッションで、保存のたびにsession.startが走り直すのを避けたいときの逃げ道になります。

設定の置き場所は環境変数(Environment)です。シェルでexportするか、起動用のラッパーで渡します。

# 対話で保存時の再読み込みを止める
CLAUDE_CODE_PLUGIN_DIR_WATCH=0 claude --plugin-dir ./first-mod
 
# 非対話のセッションでも保存時に再読み込みさせる
CLAUDE_CODE_PLUGIN_DIR_WATCH=1 claude -p "tally を数えて" \
  --plugin-dir ./first-mod

上は書式を示す例です。first-modは公式のmod作成ページで使われているサンプル名で、手元のディレクトリに読み替えます。

保存したときにmodの中で起きること

再読み込みの中身を知っておくと、開発中の「値が消えた」に慌てずに済みます。

  • hooksモジュールのregisterがもう一度呼ばれます。モジュール直下の変数は初期化し直されるため、作成ページのサンプルではcallsが0に戻り、/tallyの数え直しが始まります
  • session.startフックが、再読み込みのたびに改めて走ります。コマンドの登録などをここで行っているmodは、保存のたびに登録をやり直します
  • モジュールが設定したタイマーは止まり、新しいインスタンスが自分のタイマーを立て直します
  • 値を残したいときは$.stateを使います。セッションが終わるか/clear、/resume、/branchを実行するまで、再読み込みをまたいで残ります

壊れた保存への備えもあります。保存した内容でモジュールが読み込めなかったときは、トランスクリプトにreload failed, the previous version stays loaded:に続けて理由が出ます。その場合、直前の動いていたバージョンが走り続け、次にプラグインが再読み込みされるまで置き換わりません。

保存が失敗しても、画面の挙動は古いままです。「直したのに変わらない」と感じたら、まずトランスクリプトの再読み込み行を見ます。

Claudeにmodを編集させるときの流れ

modの修正をClaudeに頼むなら、--plugin-dirで対象のディレクトリを指して起動します。

claude --plugin-dir ./first-mod

Claudeはhooksモジュールを編集し、claude plugin validateを走らせ、報告された問題を直します。Claudeが保存したファイルはそのターンの終わりに再読み込みされるので、ターンが終われば新しいコマンドをすぐ試せます。

--plugin-dirで読んだディレクトリは保護パスに当たります。defaultとacceptEditsのモードでは、Claudeがmodを編集するたびに承認を求められます。承認の手間を減らしたいなら、保護パスの扱いはモードごとに異なり、権限モードの解説ページに表があります。

CLAUDE.mdに検証の手順を書いておくと、再読み込みと組み合わせた反復が安定します。次は書式を示す例です。

## mod の変更手順
- hooks モジュールを変えたら `claude plugin validate ./first-mod` を実行し、
  報告された警告とエラーを読んでから次の編集に進む
- 再読み込みの結果はトランスクリプトの `reload failed` 行で確かめる

claude plugin validateは、マニフェストとhooksモジュールを読み、スペルミスのあるイベント名や読めないモジュールを、セッションを起動せずに報告します。保存時の再読み込みは動作確認、validateは構造の確認、と役割を分けて使います。

対象になるもの、ならないもの

保存時の再読み込みの対象は、--plugin-dirでディレクトリから読んだmodです。次の点は区別して覚えておきます。

  • マーケットプレイスから入れたmod: 保存のたびの再読み込みの対象ではありません。シェルから導入や更新をしたあとは、開いているセッションで/reload-pluginsを実行して読み込みます。実行しなければ、次回の起動時に読み込まれます
  • CLAUDE_CODE_PLUGIN_DIRSで指定したディレクトリ: この変数は、各パスを--plugin-dirフラグと同じ方法で読み込みます。保存時の再読み込みの対象になるかは、env-varsページのCLAUDE_CODE_PLUGIN_DIR_WATCHの説明に記載がありません。複数のディレクトリを環境変数で渡す方法はCLAUDE_CODE_PLUGIN_DIRSの記事にあります
  • 管理者がdisableSideloadFlagsを設定した環境: managed settingsのこの設定は、起動時に--plugin-dirと--plugin-urlを拒否します。読み込み元のフラグ自体が通らないので、PLUGIN_DIR_WATCHを設定しても再読み込みする対象がありません

対話セッションでも、/reload-pluginsが要る場面は残ります。MCPサーバーの追加や削除のように、プロンプトキャッシュに影響する変更は別の扱いです。この点は/reload-pluginsの記事で詳しく扱っています。

CLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESHの役割と代償

  • 1にすると、非対話モードで、バックグラウンドのプラグイン導入が完了したあとにターン境界でプラグイン状態を更新する
  • 既定ではオフ
  • オフの理由は、更新がセッションの途中でシステムプロンプトを変えるため、そのターンのプロンプトキャッシュが無効になること

代償は、更新が入るターンのプロンプトキャッシュが効かなくなることです。/reload-pluginsでも同種の変更では、次のメッセージがキャッシュを使わず会話全体を読み直すという警告が出ます。

背景にあるのは、-pでのプラグイン導入の既定の動きです。CLAUDE_CODE_SYNC_PLUGIN_INSTALLを設定しない場合、プラグインはバックグラウンドで導入され、最初のターンでは使えないことがあります。導入の完了を待つ方法は2つあります。

方法動き向く場面
CLAUDE_CODE_SYNC_PLUGIN_INSTALL=1動き最初の問い合わせの前に導入の完了を待つ。待ち時間の上限はCLAUDE_CODE_SYNC_PLUGIN_INSTALL_TIMEOUT_MSで決める向く場面最初のターンからプラグインが要る実行
CLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESH=1動き最初は待たずに始め、導入が終わったターン境界で状態を更新する向く場面起動を待たせたくないが、途中からプラグインを使いたい実行

後者は、更新が入るターンでキャッシュが無効になります。最初のターンからプラグインを使うなら前者です。最初のターンはプラグインなしでも進められる実行なら、後者も選べます。

導入を待つ側の変数はCLAUDE_CODE_SYNC_PLUGIN_INSTALLの記事で詳しく扱っています。

非対話で更新を手動で起こす方法

2つの変数を使わずに、プラグインの更新を狙ったタイミングで入れる方法もあります。/reload-pluginsは、-p、デスクトップアプリ、Agent SDKのような対話用ターミナルがないセッションでも実行できます。v2.1.260以降が必要です。

注意点が2つあります。このコマンドは、-pのプロンプトのように「自分でセッションに入力した」場合にだけ動きます。Remote ControlやSlack経由で届いた場合は、/reload-plugins isn't available over a remote connection in this session.と返って何も再読み込みしません。

また、この種のセッションでの再読み込みは、プラグインのMCPサーバーを接続したり切断したりしません。そうした変更は次のセッションで反映されます。

Agent SDKからはreloadPlugins()が同じ役割を持ちます。{ holdOnCacheImpact: true }を渡すと、会話のプロンプトキャッシュを無効にする再読み込みを適用せず保留できます。このオプションにはAgent SDK v0.3.268以降が必要です。

どの設定を選ぶか

状況見る設定
対話でmodを書き換えながら試す見る設定何も設定しない(既定でオン)
保存のたびのsession.startの再実行を止めたい見る設定CLAUDE_CODE_PLUGIN_DIR_WATCH=0
長く動く非対話セッションでmodを書き換える見る設定CLAUDE_CODE_PLUGIN_DIR_WATCH=1
-pで最初のターンからプラグインを使う見る設定CLAUDE_CODE_SYNC_PLUGIN_INSTALL=1
-pで導入完了後に状態を更新したい見る設定CLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESH=1(キャッシュ無効化を許容)
指定したタイミングで更新したい見る設定-pのプロンプトで/reload-plugins

modそのものの作り方は、Claude Codeのmodを80行で作る手順にあります。

つまずきやすい点

保存しても反映されないときは、次の順に疑います。

  1. 起動方法の確認から始めます。--plugin-dirで読んだmodだけが対象で、マーケットプレイス経由で入れたmodは/reload-pluginsが要ります
  2. 非対話セッションならCLAUDE_CODE_PLUGIN_DIR_WATCH=1が必要です
  3. シェルのプロファイルやラッパーにCLAUDE_CODE_PLUGIN_DIR_WATCH=0が残っていないかも疑います。0は全セッションで再読み込みを止めます
  4. トランスクリプトにreload failedの行がないかを探します。出ていれば、直前のバージョンが走っています
  5. 非対話の-pなら、stderrも確かめます。--plugin-dirを付けたclaude -pでは、読み込み失敗の行が既定のテキスト出力形式でstderrに出ます

バージョンも確かめます。CLAUDE_CODE_PLUGIN_DIR_WATCHはv2.1.287以降の変数で、これより古いClaude Codeでは設定しても効きません。

まとめ

開発中のmodは保存時の再読み込みに任せ、CIではCLAUDE_CODE_SYNC_PLUGIN_INSTALLで起動時に導入を揃える組み合わせなら、プロンプトキャッシュを崩さずに済みます。CLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESHを足すのは、最初のターンをプラグインなしで進められ、キャッシュの無効化を受け入れられる実行だけです。

この記事を共有:XはてブLinkedIn