セットアップスクリプトとフックの使い分け — Claude Code
Claude Codeのクラウド環境で依存関係を用意する方法は2つ。VM自体を作るSetup scriptと、セッションのたびに走るSessionStartフックの使い分け基準をまとめました。
どちらに書くか迷ったら、まず「何を用意したいか」で決める
Claude Codeのクラウドセッションで依存関係を用意する方法は2つあります。環境設定に書くセットアップスクリプト(Setup script)と、リポジトリの .claude/settings.json に書くSessionStartフックです。どちらも「セッション開始時に何かを実行する」点は同じですが、走る場所・タイミング・キャッシュの有無が違います。
判断の軸は用意したいものの種類です。apt install で入れるOSレベルのツールや、.NETのようにプリインストールされていないツールチェーンならSetup script。npm install のようにプロジェクトの依存関係をローカルとクラウドで揃えたいならSessionStartフックです。
両方を組み合わせる構成も取れます。重いツールチェーンをSetup scriptでキャッシュに焼き込み、軽いインストールだけをフックで毎回走らせる分担です。
セッション開始時、2つはどの順で走るか
Claude Codeのクラウドセッションは、新しいVMが立ち上がってから次の順に動きます。
クラウドセッションの起動順序
- 1
環境キャッシュを確認する
キャッシュ済みの環境があれば、そのスナップショットを起点にします。無ければ次のSetup scriptが走ります。
- 2
Setup scriptを実行する(キャッシュが無いときだけ)
Claude Codeが起動する前に、rootでBashスクリプトが走ります。5分程度に収まればスナップショットとして保存されます。
- 3
Claude Codeが起動する
この時点から、リポジトリの設定ファイルが読まれます。
- 4
SessionStartフックが走る
セッションの開始・再開のたびに、キャッシュの有無に関係なく実行されます。
軸ごとの違いは次の表のとおりです。
| 評価軸 | Setup script | SessionStartフック |
|---|---|---|
| 設定場所 | Setup scriptclaude.ai/codeの環境ダイアログ(組織共有環境はOwnerが使う管理ページ) | SessionStartフックリポジトリの .claude/settings.json |
| 実行タイミング | Setup scriptClaude Code起動前、キャッシュ済み環境が無いときのみ | SessionStartフックClaude Code起動後、毎回のセッション開始・再開時 |
| 実行環境 | Setup scriptクラウドセッションのみ | SessionStartフックローカル・クラウド両方 |
| 時間の上限 | Setup script約5分を超えるとキャッシュされない | SessionStartフックcommandフックは既定600秒でキャンセル(timeout で変更可) |
| 失敗時の挙動 | Setup script0以外の終了コードでセッション起動自体が失敗する | SessionStartフックブロックはできず、セッションは続行する |
| キャッシュ | Setup scriptファイルシステムのスナップショットとして保持(約7日で失効) | SessionStartフックされない。毎回実行される |
時間の上限は、意味が逆になっている点に注意が要ります。Setup scriptの5分は「超えたらキャッシュされない」という上限で、フックの600秒は「超えたら打ち切られる」という上限です。async: true を付けたフックには、この打ち切りが適用されません。
Claudeが実行するBashコマンド側の上限は別枠です。環境変数に BASH_DEFAULT_TIMEOUT_MS と BASH_MAX_TIMEOUT_MS を足すと、その環境のセッションで延ばせます。単位はミリ秒で、BASH_DEFAULT_TIMEOUT_MS=600000 なら既定が10分になります。
症状・目的から選ぶ早見表
| やりたいこと・困りごと | 使う仕組み |
|---|---|
OSパッケージ(apt install 対象)を入れる | 使う仕組みSetup script |
| Node.js / Rubyなど言語のバージョンを切り替える | 使う仕組みSetup script |
| 重いDockerイメージを事前にpullしておく | 使う仕組みSetup script(docker compose pull を実行) |
npm install などの依存関係をローカル・クラウド共通で揃える | 使う仕組みSessionStartフック |
| クラウドセッションだけに限定した処理をする | 使う仕組みSessionStartフック + CLAUDE_CODE_REMOTE 判定 |
| 複数リポジトリを1セッションに入れて依存関係を用意する | 使う仕組みSetup script(後述のとおりフックが読まれない) |
| データベースやコンテナを毎セッション起動しておく | 使う仕組みSessionStartフック(プロセスはキャッシュされない) |
| ユーザー単位のコマンド・スキル・フックをクラウドでも効かせる | 使う仕組みリポジトリの .claude/ にコミットする(コマンドは .claude/commands/、フックは .claude/settings.json) |
最後の行の補足です。~/.claude/ 配下のユーザー設定(settings.json、skills、agents、commands)はローカルマシンに留まり、クラウドセッションには届きません。リポジトリの .claude/ に置いたものはクローンの一部として届きます。
それぞれの強みと弱み
Setup scriptとSessionStartフック
Setup script
初回実行後の結果がスナップショットとして残り、以降のセッションはインストール済みの状態から始まります。rootで走るので、OSレベルのツール導入に制約はほぼありません。弱みは時間です。5分を超えるとキャッシュされず、起動のたびに重い処理を待つことになります。
SessionStartフック
ローカルとクラウドで同じ処理が走り、「ローカルでは手で npm install したがクラウドでは忘れていた」という食い違いが起きません。弱みは、キャッシュが効かず毎回フルで走る点です。起動のレイテンシーに直結するので、依存関係が既にあるかを先に確かめてからインストールする作りが向きます。
Setup scriptにはもう1つ性質があります。キャッシュが保持するのはファイルだけで、起動していたプロセスは保持されません。スクリプトで立ち上げたデータベースやDocker Composeのスタックは、次のセッションでは動いていません。
Setup scriptを書くときの制約
Setup scriptは自由なBashスクリプトですが、書く前に押さえたい制約が3つあります。
- 終了コードを0にする: 0以外で終了するとセッションが起動しません。不安定になりがちなコマンドの末尾には
|| trueを付けます - 全体を約5分以内に収める: 超えるとキャッシュが作られません。独立した処理は
&とwaitで並列化します。どうしても収まらない単発の重いダウンロードは、バックグラウンド起動のSessionStartフックへ逃がします - インストールにはレジストリへの到達が必要: 既定のTrustedレベルはnpm・PyPI・RubyGems・crates.ioなどを含みます。ネットワークアクセスをNoneにした環境では、インストール系のコマンドがすべて失敗します
この3つを踏まえた書き方の例です。
#!/bin/bash
apt update && apt install -y shellcheck
# 独立した2つのインストールを並列化し、片方が不安定でも止めない
(npm install -g typescript || true) &
(pip install --quiet pytest || true) &
waitベースイメージそのものを別物に差し替える使い方は、現状サポートされていません。別のベースイメージが要るときは、提供イメージの上にツールを足すか、docker compose で自前のイメージをコンテナとして併走させます。
クラウドだけでフックを実行する書き方
SessionStartフックはローカルでも走ります。クラウドだけに限定するには、環境変数 CLAUDE_CODE_REMOTE で分岐します。この変数はクラウドのVMで true になり、ローカルでは設定されません。
まず、リポジトリの .claude/settings.json にフックを追加します。
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR\"/scripts/install_pkgs.sh"
}
]
}
]
}
}matcher を startup|resume にしているので、/clear や圧縮のたびには走りません。$CLAUDE_PROJECT_DIR はリポジトリのルートに解決されます。
次に、scripts/install_pkgs.sh の側でクラウド以外を早期リターンさせます。
#!/bin/bash
if [ "$CLAUDE_CODE_REMOTE" != "true" ]; then
exit 0
fi
npm install
pip install -r requirements.txt
exit 0クラウドセッションだけが npm install と pip install を実行し、ローカルは何もせず抜けます。
フックのスクリプトで環境変数を後続のBashコマンドに渡したいときは、CLAUDE_ENV_FILE に export 文を追記します。この変数はSessionStartなど一部のフックでだけ使えます。
フックが動かない・起動が遅いときの切り分け
症状ごとに、疑う順に並べます。
- ユーザーレベルのフックが動かない:
~/.claude/settings.jsonに書いたSessionStartフックはクラウドに届きません。リポジトリの設定ファイルへ移します - 複数リポジトリのセッションでフックが走らない: 複数のリポジトリを入れたセッションは、どのリポジトリの
.claude/settings.jsonのフックも読み込みません。Projectsのスレッドもこの扱いで、クローンの手前から始まり中身を読みません。この構成の依存関係はSetup scriptで入れます - 組織のフックがClaude Tagで走らない: Anthropic管理環境では、リポジトリのフックと組織のserver-managed settingsのフックが走ります。ただしClaude Tagのセッションにはserver-managed settingsが届かず、そちら経由のフックは動きません
- 次のセッションの起動が急に遅くなった: Setup scriptは、スクリプト自体や許可ドメインを変更したときと、キャッシュが約7日で失効したときに再実行されます。遅さはキャッシュの再構築を疑います
- Setup scriptを書き換えたのに既存セッションに反映されない: 待機から復帰したVMではSetup scriptは走りません。反映されるのはVMが回収されて作り直されたときだけです。すぐ反映したいなら、セッション内でコマンドを実行するか新しいセッションを始めます。環境変数も同じで、変更後の既存セッションは、VMが次に復帰または再構築されるまで最後に読んだ値のままです
- 新しいセッションがセットアップ中に固まる・失敗する: スクリプトが約5分の予算を超えている可能性が高いです。大きなDockerイメージのpull、依存関係の全量同期、モデルの重みのダウンロードが典型で、直列に並べると超えやすくなります。長い再試行用の
sleepも予算を食います - Bunなど一部のパッケージマネージャーが失敗する: Anthropic管理環境の外向き通信はすべてセキュリティプロキシを通り、Bunのように相性が悪いものがあります。Bunはプリインストールされていますが、パッケージのfetchでproxyとの互換性問題が知られています。フックから呼んでも同じです
Dockerイメージと追加パッケージのキャッシュ
Setup scriptに docker compose pull や docker compose build を書くと、pull済みのイメージがディスクに残るので環境キャッシュの対象になります。コンテナの起動は別です。次のセッションでも自動では立ち上がらないため、Claudeが docker compose up を実行するか、SessionStartフックで起動します。コンテナ操作をMCP経由で任せる場合の比較はDocker/Podman/Kubernetes MCPの選び方にあります。
セッションの途中でClaudeに追加インストールさせることもできますが、その分は他のセッションに引き継がれません。繰り返し使うパッケージは、気づいた時点でSetup scriptに足しておくと、次回から省けます。
組織共有環境とClaude Tagのチャンネルで設定する場合
Team・Enterpriseプランでは、Ownerが組織の全メンバーに共有する環境を作れます。Setup scriptも他の設定と並んで、管理設定のCloud environmentsページで編集します。Adminロールはこのページを開けません。
メンバーが環境セレクターから共有環境の設定アイコンを開くと、読み取り専用の概要が表示されます。個人の設定でその共有環境のSetup scriptを書き換えることはできません。個人用の追加インストールが必要なら、リポジトリ側のSessionStartフックに足します。
Claude Tagは、Slackと連携するTeam・Enterprise向けの統合です。チャンネルの中ではClaudeが組織の共有アイデンティティとして動くため、チャンネルのセッションは組織レベルの環境(共有環境かセルフホスト環境)しか使えません。.NETのようにプリインストールされていないツールチェーンをチャンネルに用意するには、Ownerが.NETを入れるSetup script付きの共有環境を作ります。その環境を組織の既定にするか、チャンネルに固定します。
自社ハードウェアで動かすセルフホスト環境では、ツールチェーンをRunnerイメージに入れるか、ラッパースクリプトで用意します。SessionStartフックはそのまま走ります。加えて、運用者がRunnerホストの ~/.claude/ から取り込んだフックと、Runnerイメージの管理設定ファイルのフックも走ります。
Setup scriptと環境変数には、もう1つ注意点があります。環境の利用者なら誰でも読めるため、秘密情報は置けません。環境ダイアログの環境変数欄にも、その旨の注記があります。ProとMaxのプランでは、エージェントのプロキシがAPI認証情報として付与できるキーを別に登録します。TeamとEnterpriseにはこのAPI認証情報の機能がまだ無く、キーを渡す経路は環境変数だけです。その値は共有環境のメンバー全員に読まれる前提で扱います。
まとめ
迷ったときは「そのセッションを捨てたあとも、次回に残したいか」で決めます。残したいならSetup script、毎回やり直してよいならSessionStartフックです。SessionStart以外のタイミングで自動化したいときは、フックの全イベント一覧が出発点になります。