Claude CodeをCIや自動スクリプトに組み込んでいて、依存の準備や定期掃除を決まったタイミングで走らせたい人向け
料理ブログのCIでテスト前に npm install を一度だけ通したい、家計簿アプリの夜間ジョブで古いログを定期的に消したい、といった「毎回の起動とは切り離した一回限りの準備・掃除」を明示のタイミングで走らせたい場面で、settings.json に登録したうえで claude --init-only や claude -p --maintenance を叩く
Setup は、CI や自動スクリプトの中で「一回だけ済ませておきたい準備」を走らせるためのフックです。たとえばテストを回す前に npm install を一度だけ通しておく、みたいな用途。ここが大事なんですが、普通に claude と打って起動しても、このフックは一切動きません。起動するときに専用の指定をその場で足したときだけ発火します。
言い換えると、Setup は「毎回の起動とは切り離した、明示的な準備の入口」です。毎セッションの初期化をやりたいなら別のフック(SessionStart)の仕事なので、そこを取り違えると永遠に動かないフックを書くことになります。
噛み砕くと
新しい職場に道具を運び込む「引っ越しの日」をイメージすると近いです。机を組み立てたり、共有フォルダを作ったり、その日にだけやる準備がある。出社初日のあれです。でも翌日からの「毎朝PCを立ち上げる」作業は、それとは別物ですよね。
Setup はこの「引っ越しの日」専用のフックです。あなたが「今日は準備の日だ」と宣言したときだけ動く。毎朝の起動では動かない。だからこそ、重い npm install や古いログの掃除みたいな処理を、毎回のジャマにならない形で仕込めます。
毎朝の準備をここに書くと、一生発火しません。
大事な前提:Setup は「起動時にその場で指定」しないと一生動かない
Setup を動かすには2つの準備がいります。1つめは、プロジェクトの .claude/settings.json にフックを登録すること。登録したら /hooks で一覧に出ているか確認できます。2つめが本題で、起動するときに次のどれかをコマンドの後ろに足すこと。これがないと登録してあっても発火しません。
claude --init-onlyclaude -p --initclaude -p --maintenance
そして、書けるフックの種類は command(コマンドを実行する型)と mcp_tool の2つだけです。http / prompt / agent 型は書いても動きません。
「料理ブログのCI」を例に、実際の手順を見る
料理ブログのプロジェクトをCIで回すとします。テストを走らせる前に、必要なものを入れる npm install を一度だけ通しておきたい。これを Setup でやってみます。
ステップ1: .claude/settings.json に Setup フックを登録する
設定ファイルに、Setup というイベント名 → どの起動指定で動くか(matcher)→ 実際に走らせる処理、の3段で書きます。matcher を init にしておくと、後で出てくる --init-only や -p --init で起動したときに発火します。
{
"hooks": {
"Setup": [
{
"matcher": "init",
"hooks": [
{ "type": "command", "command": "npm install" }
]
}
]
}
}
matcher に入る値は2つだけ。init は claude --init-only または claude -p --init で、maintenance は claude -p --maintenance で発火します。どの起動指定が引き金になったかが、そのまま matcher の名前になっている、という対応です。
ステップ2: claude --init-only で準備を走らせる
CIのスクリプトの中で、テストの手前にこの1行を置きます。
$ claude --init-only
これで Setup に登録した npm install が動きます。準備が済んだら、続けてCIが普段のテストコマンドを叩く、という流れです。
ステップ3: 会話が始まらずに終わることを確認する
ここで初心者がやりがちな勘違いがあります。「--init-only を打ったら対話画面が出てくるはず」と思って待ち続けてしまうやつです。実際は逆で、--init-only は Setup フックと、SessionStart フックのうち matcher が startup のものを走らせたあと、会話を一切始めずにそのまま終了します。CIのように「準備だけして次に進む」用途にちょうど合う挙動です。
つまり Setup 単独ではなく、SessionStart(startup) も一緒に動く点は覚えておくといいです。
ステップ4: Claude に情報を渡したいときの返し方
「依存を入れました」みたいな情報を Claude 側に伝えたいことがあります。ここに最大の落とし穴があって、ただ echo で文字を出しても Claude には届きません。素の出力はデバッグログに書かれるだけです。Claude の手元に渡したいなら、決まった形のJSONで additionalContext を返します。これは公式が示している形そのままです。
{
"hookSpecificOutput": {
"hookEventName": "Setup",
"additionalContext": "Dependencies installed: node_modules, .venv"
}
}
複数のフックがそれぞれ additionalContext を返した場合は、その中身がつなげて Claude に渡されます。
ステップ5: 入力として届く中身を知っておく
Setup フックが受け取る入力には、共通の項目に加えて trigger という項目が1つ付きます。中身は "init" か "maintenance" のどちらか。どっちの起動で呼ばれたかをスクリプト側で見分けられます。公式の入力例はこの形です。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Setup",
"trigger": "init"
}
ステップ6: maintenance 側で定期掃除をやる
準備だけじゃなく、定期的な掃除にも使えます。古いログを消す、みたいな処理を matcher maintenance で登録しておき、CIの夜間ジョブから次のように呼ぶ形です。
$ claude -p --maintenance
掃除は init とは別の matcher にしておくと、準備のときに掃除まで走る事故を防げます。
つまり Setup は何をしてくれるのか
- やってくれる:
--init-onlyや-p --init/-p --maintenanceで起動したときだけ、npm installのような一回限りの準備や定期掃除を走らせる - やってくれる(もう1つ):
CLAUDE_ENV_FILEという置き場所に書き込んだ設定値を、そのセッション内の後続の Bash コマンドへ引き継ぐ。additionalContextが「Claude の会話に文字情報を渡す」道なのに対し、こちらは「後続のコマンドが読める値を渡す」道で、別物です - やってくれない: 普通の起動での自動実行(毎回は動かない)、処理を止めて起動を中断させること(Setup はブロック不可)、素の出力をそのまま Claude に届けること。これは
additionalContext経由でないと渡りません - 意味が薄い場面: 毎セッションやりたい初期化をここに書くこと。それは SessionStart の仕事で、Setup に書くと普段は一切発火しない
使いどころ3シナリオ(具体題材で再現)
シナリオ1: 料理ブログのCIで、テスト前に依存を一回だけ入れたいとき
料理ブログのテストを回す前に npm install を通しておきたい。matcher init で npm install を登録し、CIスクリプトのテスト手前に claude --init-only を1行置く。これで毎回の起動を汚さずに、準備だけを明示のタイミングで済ませられます。準備が終わると会話は始まらず終了するので、CIはそのまま次のテスト工程へ進めます。
シナリオ2: 家計簿アプリの夜間ジョブで、古いログを定期掃除したいとき
家計簿アプリのプロジェクトで、溜まった古いログを毎晩消したい。掃除コマンドを matcher maintenance に登録し、夜間ジョブから claude -p --maintenance を叩きます。準備(init)と掃除(maintenance)を別の matcher に分けておけば、テスト前の準備のついでに掃除が走るような取り違えを防げます。
シナリオ3: 配布するプラグインで、初回だけ依存を入れたいとき
自作プラグインを配布していて、内部で使うライブラリを入れておきたい。ただし Setup だけに頼ると、--init-only を叩かない人の環境では一生入りません。公式が勧めているのは「初回に使うとき存在チェックして、無ければ入れる」やり方です。具体的には、${CLAUDE_PLUGIN_DATA}/node_modules というフォルダがあるか確かめて、無ければ npm install を走らせる形。Setup は「明示的に準備を回す入口」、初回チェックは「取りこぼし防止の保険」という二段構えにすると安全です。
初心者が踏みやすい落とし穴
- 普通に
claudeと打って「動かない」と悩む。Setup は通常起動では発火しません。--init-onlyなどを起動時にその場で足したときだけです。 - 対話セッションで
claude --initと打てば動くと思い込む。--init/--maintenanceは-p(画面に結果を出して終わるモード)と組み合わせたときだけ発火します。対話セッションに付けても現状は動きません。 echoした文字が Claude に届くと思っている。素の出力はデバッグログ行きで、画面にも Claude にも出ません。伝えたいならadditionalContextをJSONで返します。- exit code 2 で起動を止められると思っている。Setup はブロック不可です。exit code 2 のときは stderr がユーザーに表示されるだけで、それ以外の非ゼロは
--verboseを付けたときだけ stderr が見えます。どちらにしても処理は続きます。 - 「フックが動いてない」と誤診する。素の出力が普段見えないせいで起きがちです。動作確認は
--verboseを付けるか、デバッグログを見にいくのが確実。 - プラグインの依存インストールを Setup だけに頼る。
--init-onlyを叩かない人の環境で壊れます。初回使用時に${CLAUDE_PLUGIN_DATA}/node_modulesがあるか確かめて無ければ入れる、という保険と併用するのが公式推奨。 mcp_tool型を初回から当てにする。SessionStart と Setup はMCPサーバの接続が終わる前に動くことが多く、初回は「not connected」エラーが出る前提で組む必要があります。- 毎セッションやりたい処理を Setup に書く。それは SessionStart の役目です。Setup に書くと普段は一切走りません。
- 後続の Bash コマンドに値を渡したいのに
additionalContextだけ使う。additionalContextは Claude の会話に届く文字列で、後続のコマンドが読める値にはなりません。値を持ち越したいときはCLAUDE_ENV_FILEの指す先に公式サンプルの形(例:echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"、追記の>>で他のフックが書いた分を消さない)で書き込みます。ここに書いた値はそのセッション内の後続の Bash コマンドで読めます。この置き場所が使えるのは SessionStart / Setup / CwdChanged / FileChanged の4イベントだけです。
書き方
claude --init-only # matcher: init で登録した Setup を発火
claude -p --init # 同じく init を発火(画面出力モード)
claude -p --maintenance # matcher: maintenance を発火(定期掃除など)
やってみるとこうなる
入力
claude --init-only
出力例
(会話は始まらず、登録した npm install などが走って終了する。Claude に情報を渡したいときは additionalContext をJSONで返す)
{
"hookSpecificOutput": {
"hookEventName": "Setup",
"additionalContext": "Dependencies installed: node_modules, .venv"
}
}
このページに出てきた言葉
- Setup
- CIや自動スクリプトでの一回限りの準備・定期掃除を、起動時の明示指定(<code>--init-only</code> など)でだけ走らせるフック
- --init-only
- 起動時にこれを足すと Setup と SessionStart(startup) を走らせ、会話を始めずに終了する起動の仕方
- matcher
- そのフックをどの起動指定で動かすか絞り込む値。Setup では <code>init</code> と <code>maintenance</code> の2つだけ
- trigger
- Setup フックの入力に付く項目。<code>"init"</code> か <code>"maintenance"</code> のどちらで呼ばれたかが入る
- additionalContext
- フックから Claude の手元に文字情報を渡すための、決まった返し方の項目名。素の出力はデバッグログ行きで Claude には届かない
- SessionStart
- 毎回の会話の始まりに動く別のフック。毎セッションの初期化はこちらの役目
- CI
- コードを変えるたびにテストや確認を人手を介さず自動で走らせる仕組み