Claude CodeのpolicyHelperで管理ポリシーを動的に取得する設定
policyHelperの仕組みとmanaged-settings.jsonでの登録手順、起動時にセッションを拒否する失敗時の挙動をまとめます。
policyHelperは、Claude Codeの管理ポリシー(managed settings)を静的なファイルではなく実行ファイルの出力から組み立てる設定です。起動のたびにデバイスの状態や識別情報をもとにポリシーを計算し直せるので、managed-settings.jsonを書き換える運用では組みにくい「デバイスごとに条件分岐するポリシー」が作れます。
Claude CodeのpolicyHelperとは何か
policyHelperとは、組織が配置した実行ファイルをClaude Codeが起動時に呼び出す仕組みです。その標準出力が、そのセッションの管理ポリシーとして扱われます。「このデバイスは会社支給か」「このユーザーはどの部署か」といった条件を、ヘルパー側のロジックで判定してポリシーに反映できます。
ヘルパーは最初のプロンプトを受け付ける前に実行されます。キーを配らない限り何も動かず、通常の静的な管理ポリシーがそのまま使われます。
ヘルパーが効くまでの流れ
- 1
配信元を選ぶ
managed settingsの配信元のうち、ポリシーのキーを持つ最上位の配信元が選ばれます。それが構成プロファイル・HKLM・
managed-settings.jsonのどれかであれば、policyHelperキーが読まれます。 - 2
ヘルパーを実行する
引数なしで起動し、環境変数
CLAUDE_CODE_VERSIONが渡されます。標準出力は1MiBまでです。 - 3
出力を唯一のポリシーにする
managedSettingsキーの下のオブジェクトが、そのセッションのポリシーのすべてになります。
policyHelperが動く配信元と動かない配信元
policyHelperのスコープはManagedですが、読み込まれる配信元は3つだけです。macOSの構成プロファイル、WindowsのHKLMレジストリ、そしてmanaged-settings.jsonです。複数の管理ソースがある環境では、既定(first-wins)だと最優先の配信元以外は無視されます。managedSourcesBehaviorを"merge"にした場合(v2.1.242以降)は、管理者が配布したすべての管理ソース(HKCUを除く)が1つのポリシーに合成されます。ただしpolicyHelperキー自体は、最上位の配信元からだけ読まれます。
| 配信元 | ヘルパーは動くか | 補足 |
|---|---|---|
| macOSの構成プロファイル(plist) | ヘルパーは動くか動く | 補足com.anthropic.claudecodeドメインに書く |
| WindowsのHKLMレジストリ | ヘルパーは動くか動く | 補足HKLM\SOFTWARE\Policies\ClaudeCodeのSettings値 |
managed-settings.json(ファイル配信) | ヘルパーは動くか動く | 補足配信先のOSごとのシステムディレクトリに置く |
| claude.aiのサーバー管理設定 | ヘルパーは動くか動かない | 補足この配信元が選ばれるとヘルパーより優先される |
| WindowsのHKCUレジストリ | ヘルパーは動くか動かない | 補足policyHelperキーを読まない配信元 |
| 埋め込みホストのparent settings | ヘルパーは動くか動かない | 補足Claude Desktop等が渡す値は対象外 |
サーバー管理設定があとから外れたと分かった時点では、次回起動を待たずにその場でヘルパーが実行されます。この実行が失敗したときは、起動時の失敗と同じ理由を出してセッションが終了します。
Coworkのセッションは、動く面がさらに絞られます。
- ユーザーの手元マシンで動くCowork: ヘルパーの設定元であるMDMポリシーと
managed-settings.jsonを読むので、policyHelperも動きます。 requireCoworkFullVmSandboxを設定した完全VMサンドボックス: VM内にはデバイスのMDMポリシーもmanaged-settings.jsonもありません。設定元が存在しないので、ヘルパーも使えません。- Anthropic管理のVM上で動くリモートCowork: デバイスのポリシーを読めないため、同じくヘルパーは使えません。
組織にCoworkの利用者がいる場合は、どの実行形態で動いているかを先に洗い出します。
ヘルパー実行ファイルを書く
ヘルパーは標準出力に1つのJSONオブジェクトを書き、managedSettingsキーの下に実際のポリシーを入れます。次の最小例はシェルスクリプトで、ホスト名がbuild-で始まる端末にだけ緩い拒否ルールを返します。それ以外の端末にはcurlの実行も拒否するルールを足します。ただしBash(curl:*)は/usr/bin/curlのようなフルパス呼び出しやsh -c経由の呼び出しには一致しないので、完全な遮断ではありません。
#!/bin/sh
case "$(hostname)" in
build-*) deny='"Read(//etc/secrets/**)"' ;;
*) deny='"Read(//etc/secrets/**)", "Bash(curl:*)"' ;;
esac
printf '{"managedSettings":{"permissions":{"deny":[%s]},"env":{"HELPER_FOR":"%s"}}}\n' "$deny" "$CLAUDE_CODE_VERSION"ヘルパーには実行権限(chmod +x)を付け、pathで指定した場所に置きます。build-以外のホスト名でCLAUDE_CODE_VERSION=2.1.287を渡して実行した出力は次のとおりです。ヘルパー単体の実行で、整形のためpython3 -m json.toolを通しています。
{
"managedSettings": {
"permissions": {
"deny": ["Read(//etc/secrets/**)", "Bash(curl:*)"]
},
"env": { "HELPER_FOR": "2.1.287" }
}
}managedSettingsキーを持たないオブジェクトを返すと、ヘルパーの出力からはポリシーが適用されず、他の配信元の管理ポリシーが通常どおり適用されます。エラーも出ないので、期待どおりに効かないときはまずこのキーの有無を見ます。
ヘルパーが有効な間は、他の配信元が無視されます。通常なら複数の管理ソースから読まれるキー(サンドボックスのロック系キーやenvのマージなど)も、ヘルパーの出力からしか読まれません。例外はforceRemoteSettingsRefreshで、これは起動時の専用ルールに従います。
Claude Desktopのようなホストアプリが渡すparent settingsも、ヘルパーが有効な間は一切マージされません。parentSettingsBehaviorをmergeにしていても同じです。ホスト側の権限ルールをあてにしている構成では、ヘルパー導入前後でその部分が消えないか確認します。
managed-settings.jsonでpolicyHelperを登録する
登録の起点はpolicyHelperキーで、持つフィールドは3つです。
policyHelperの3フィールド
path(必須)
ヘルパーの絶対パスです。
.や..を含められません。WindowsではドライブレターまたはUNCパスで、.exe拡張子が必須です。timeoutMs
既定は10000ミリ秒で、最小は1000ミリ秒です。これを超えると、その実行は失敗として扱われます。
refreshIntervalMs
既定は未設定で、起動時の1回だけ実行されます。設定するなら0か60000ミリ秒以上で、0は再実行の無効化です。再実行されるのは出力の取得だけで、
policyHelperエントリの新規追加や変更は次回起動で有効になります。
{
"policyHelper": {
"path": "/usr/local/bin/claude-policy",
"timeoutMs": 5000,
"refreshIntervalMs": 300000
}
}pathが上の形式から外れると、ヘルパーの実行が失敗し、起動時ならセッションが始まりません。一方、timeoutMsが最小値を下回る場合や、policyHelperにパス文字列だけを書いた場合は無効なエントリとして扱われます。このときはヘルパーを動かさず、残りの管理ポリシーだけで起動します。
ヘルパーを無効化したいときは、設定した配信元からキーそのものを削除します。無効化用の専用フラグはありません。
複数チームがmanaged-settings.d/にファイルを分けて配置している場合は、読み込み順に注意します。managed-settings.jsonが先に、続いて*.jsonがアルファベット順にマージされ、単一値は後のファイルが前のファイルを置き換えます。ネストしたブロックはキーごとにマージされ、各キーは単一値の規則に従います。この規則をpolicyHelperに当てはめると、2つのチームが別々のpathを指定すれば後のファイルのpathが勝ち、片方のチームだけがtimeoutMsを書いていればその値が残ると考えられます。意図しない組み合わせにならないよう、policyHelperはどれか1つのファイルにまとめて書きます。
ヘルパーが失敗したとき、セッションはどうなるか
失敗の扱いは、いつ失敗したかで分かれます。
失敗した時点で挙動が変わる
起動時の実行
セッションの開始を拒否し、理由を表示します。対話セッション・claude -p・Agent SDKセッション・バックグラウンドセッション・ほとんどのサブコマンドが対象です。
バックグラウンドの再実行
セッションは止めず、直前に成功したポリシーを保持します。/statusには、次に成功するまで失敗理由が出続けます。
失敗と判定される条件は次のとおりです。
pathの形式が不正pathに通常ファイルが存在しない(ネットワークマウントが応答しない場合も、timeoutMsの予算内でここに含まれる)- ヘルパーが非ゼロ終了する
timeoutMs以内に終了しない- 実行できない(実行権限がないなど)
- 標準出力または標準エラー出力が1MiBを超える
- 標準出力が単一のJSONオブジェクトでない
managedSettingsのスキーマ違反が修復できない
通常の管理ポリシーでは、修復できない値は個別に捨てられてセッションは続きます。ヘルパーの出力はもっと厳格で、同じ修復を試みたうえで、残った違反が1つでもあれば実行全体が失敗します。
非ゼロ終了時は、ヘルパーの標準エラー出力(それが空なら標準出力)が理由に含まれます。タイムアウト時はヘルパーの出力を含めず、timeoutMsの上限値だけが理由に出ます。
この挙動は意図された仕様で、可用性が必要なヘルパーは自前のキャッシュから返してexit 0で終わらせる設計が前提です。リモートAPIを呼ぶだけのヘルパーは、そのAPIの障害がそのまま全端末のClaude Code起動障害になります。
動かないときの確認順
ポリシーが意図どおりに効かないときは、次の順に見ていきます。
/statusのSetting sources行で、選ばれた配信元を見る。ヘルパー由来のときは(helper)と表示される。サーバー管理設定が選ばれていれば、managed-settings.jsonにpolicyHelperを書いても動かないclaude doctorで却下されたエントリを見る。無効なエントリが配信元とフィールドつきで一覧になる。対話セッションでは起動時に同じ内容のダイアログも出る- 非対話の
-p実行では、同じ要約が標準エラー出力に出る - ヘルパー自体の不具合が疑わしいときは
--debugを付けて起動する。毎回の実行の標準エラー出力がデバッグログに書かれる。失敗はしないが期待したポリシーを返さないヘルパーを追うときに使える
policyHelperと他の配信手段をどう使い分けるか
managed settingsの配布方法全体は、Claude Code組織管理ガイドにあります。availableModelsによるモデル制限やrequiredMinimumVersionによるバージョン強制といった、より広い統制の設計も同じ記事の範囲です。policyHelperはその中の1つの配信手段です。
管理設定が壊れたときの起動拒否は、policyHelper固有の挙動ではありません。通常の管理ポリシーが不正な形式のときも、Claude Codeは起動を拒否します。Claude Code v2.1.259のリリースノートで扱った変更と同じ系統の設計です。
MDMでの配布手順はClaude Code Desktop SSOとデバイス管理ポリシーの設定で扱っています。macOSの構成プロファイルやWindowsのレジストリへの書き込みが対象です。
まとめ
すでにmanaged settingsを配布している組織で、デバイスの属性ごとにポリシーを変えたい要件が出てきた段階で検討する設定です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。