WorktreeCreate/WorktreeRemove hookでworktreeの作成・削除を差し替える
Claude Codeのgit worktree作成・削除を、WorktreeCreate/WorktreeRemove hookで別のVCSに差し替える手順と、パス検証・exit codeの注意点を解説します。
WorktreeCreate/WorktreeRemove hookとは
WorktreeCreateとWorktreeRemoveは、Claude Codeがworktreeを作成・削除するタイミングで発火するフックです。対象になるのはclaude --worktreeで起動したセッション、isolation: "worktree"を指定したSub-agent、そしてバックグラウンドセッションの3パターン。いずれもClaude Codeが作業コピーを隔離する場面で共通して使われます。worktree隔離そのものの仕組みと落とし穴は既に扱っているので、本記事はその作成・削除処理をフックで差し替える方法に絞ります。
既定の動作はシンプルです。Claude Codeはgit worktreeコマンドでこの隔離コピーを作ります。WorktreeCreateフックを設定すると、この既定のgit操作は完全に置き換わります。SVNやPerforce、Mercurialなど、gitを使わないバージョン管理システムでもClaude Codeのworktree隔離を使いたいときに出番があります。
対になるWorktreeRemoveは後片付けを担当します。--worktreeセッションの終了時、Sub-agentの完了時、バックグラウンドセッションの削除時に発火し、gitベースの構成ではgit worktree removeによる自動クリーンアップが標準で走ります。WorktreeCreateで独自VCSに差し替えた場合は、対応するWorktreeRemoveを書かないとディレクトリがディスクに残り続けます。
標準のgit worktree運用と何が変わるか
WorktreeCreateを設定する前に押さえておきたい前提が1つあります。このフックは既定の挙動を丸ごと置き換えるという点です。部分的な追加処理ではなく、完全な代替として動きます。
その結果、.worktreeincludeによるgitignore対象ファイルのコピーは処理されません。.envのようなローカル設定ファイルを新しいworktreeに持ち込みたい場合は、フックスクリプト自身の中でコピー処理を書く必要があります。gitのworktree作成が持っていた副次的な便利機能は、フックを設定した瞬間にすべて自分で肩代わりすることになります。
もう1つの前提は、この2つのイベントにmatcherが効かないことです。PreToolUsehookのようにツール名で絞り込む余地はなく、条件を分けたいならフックスクリプトの内部でJSON入力を見て分岐します。標準の許可/拒否モデルに乗らず、成功・失敗やexit codeだけで結果が決まる点は、追加自体をブロックできないDirectoryAddedhookにも通じる設計です。
手順1: WorktreeCreateでSVNの作業コピーを作る
WorktreeCreateフックへの入力には、他のイベント共通のsession_id・cwdなどに加えてnameフィールドが渡ります。ユーザーが指定するか自動生成されるworktreeのスラッグ識別子で、例えばbold-oak-a3f2のような値です。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeCreate",
"name": "feature-auth"
}このフックは通常の許可/拒否のdecision controlを使いません。成功・失敗そのものが結果を決めます。求められているのは、作成したworktreeディレクトリへのパスを返すことだけです。
- command hook: 標準出力の最後の空でない行にパスをprintする。Claude CodeはANSIエスケープを除去してから読むので、
echoの前に出るシェルの起動バナー自体は無視される。パス以外の出力は必ずstderrへ逃がす - HTTP hook: レスポンスボディで
{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }を返す
設定例です。
{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
}
]
}
]
}
}標準入力からnameを読み、新しいディレクトリへSVNのチェックアウトを実行し、最後にパスをechoしています。チェックアウトの進捗ログはすべて>&2でstderrに送っているのがポイントで、標準出力に混ざるとClaude Codeがログ行をworktreeパスと誤読します。
フックが失敗する、あるいはパスを一切出力しない場合、worktreeの作成自体がエラーで失敗します。systemMessageやcontinueといった出力フィールドはこのイベントでは読み捨てられるので、進捗をユーザーに伝える手段としては使えません。
手順2: WorktreeRemoveで対応する後片付けを書く
WorktreeRemoveの入力には、WorktreeCreateが返したパスがそのままworktree_pathフィールドで渡ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}こちらはexit codeが結果を決めます。0以外のexit codeで終わり、かつworktree_pathのディレクトリがまだ存在していれば、削除は失敗扱いになります。ディレクトリはディスクに残り、コマンドとstderrはデバッグログに記録されます。
{
"hooks": {
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
}
]
}
]
}
}WorktreeCreateのSVNチェックアウトと対にしてrm -rfで消すだけの最小構成です。systemMessage・continueを含むJSON出力フィールドは、WorktreeCreateと同様にこのイベントでも読み捨てられます。
#!/bin/bash
# WorktreeRemove: SVNの作業コピーを削除し、失敗時はログに残す
input=$(cat)
path=$(echo "$input" | jq -r '.worktree_path')
if [ ! -d "$path" ]; then
exit 0
fi
rm -rf "$path" 2>>"$HOME/.claude/worktree-remove.log" || {
echo "削除に失敗しました: $path" >&2
exit 1
}削除前に存在確認を入れているのは、二重に発火した場合や、すでに手動で消されたパスに対して余計なエラーを出さないためです。
パスの安全確認とバージョン境界
WorktreeCreateが返したパスは無条件に信用されるわけではありません。相対パスならフックの実行ディレクトリを基準に解決され、.や..セグメントは正規化されます。解決後のパスがディレクトリとして開けない場合、セッションはエラーを表示してexit code 1で終了します。
WorktreeRemove側にも同様の検証があります。バックグラウンドセッションを削除するとき、Claude Codeは保存済みのworktreeパスを確認し、シンボリックリンクそのもの、あるいはリポジトリルート配下のシンボリックリンクを経由するパスを拒否します。これもv2.1.216で入った変更で、それより前は保存済みパスに対して検証なしでフックが実行されていました。
WorktreeCreate/WorktreeRemoveの挙動を1枚で見る
| 観点 | WorktreeCreate | WorktreeRemove |
|---|---|---|
| 結果の決め方 | WorktreeCreate成功/失敗とパスの有無 | WorktreeRemoveexit codeとディレクトリの残存確認 |
| 追加の入力フィールド | WorktreeCreatename(worktreeのスラッグ) | WorktreeRemoveworktree_path(絶対パス) |
| 出力の返し方 | WorktreeCreatecommand=stdout最終行 / HTTP=worktreePath | WorktreeRemove使わない(exit codeのみ) |
| 読み捨てられる出力 | WorktreeCreatesystemMessage / continue | WorktreeRemovesystemMessage / continue |
| matcher | WorktreeCreate非対応(常に発火) | WorktreeRemove非対応(常に発火) |
両イベントとも標準の許可/拒否モデルには乗っておらず、ifフィールドによる個別の絞り込みもできません。分岐が必要なら、フックスクリプト側でJSON入力の中身を見て処理を振り分けます。
追加からHTTP対応までのバージョン履歴
WorktreeCreate/WorktreeRemoveは2つのイベントとして同時に追加されたわけではなく、非gitのVCS利用者向けに段階的に機能が足されてきました。
| バージョン | 変更内容 |
|---|---|
| v2.1.50 | 変更内容WorktreeCreate/WorktreeRemoveイベントを追加。非gitVCSでのセットアップ・後片付けが可能に |
| v2.1.69 | 変更内容プラグイン由来のWorktreeCreate/WorktreeRemoveフックが無視されていた不具合を修正 |
| v2.1.84 | 変更内容WorktreeCreateのHTTP hook対応を追加(hookSpecificOutput.worktreePathでパスを返せるように) |
| v2.1.85 | 変更内容非gitリポジトリで--worktreeがWorktreeCreateフックの実行前にエラー終了していた不具合を修正 |
| v2.1.144 | 変更内容非gitVCSでWorktreeCreateフックを使うバックグラウンドセッションにも、worktree隔離のガードが適用されるよう修正 |
| v2.1.203 | 変更内容非gitディレクトリから起動したバックグラウンドセッションで、WorktreeCreateフック設定時にファイル編集ができなかった不具合を修正 |
初期のv2.1.50時点ではcommand hookのみの対応で、HTTP hookでパスを返せるようになったのはv2.1.84からです。既存の設定をHTTP hookへ移行する場合は、対象バージョンがv2.1.84以降かを確認しておく必要があります。バックグラウンドセッションまわりの不具合修正がv2.1.144・v2.1.203と続いているのも見て取れ、非gitVCS×バックグラウンドセッションの組み合わせは比較的こなれてきたのが最近という位置付けです。
Worktree hookはどんな場面で使うか
このフックの主な使いどころは、チーム標準のバージョン管理システムがgit以外の場合です。SVNやPerforce管理下のリポジトリでもClaude Codeの並列セッション隔離を使いたいなら、WorktreeCreateでそのVCSのチェックアウト処理を書き、WorktreeRemoveで対応する削除処理を書けば、gitに依存しない隔離ワークフローが組めます。
もう1つの用途は、隔離コピーの作成時に社内独自の初期化を挟みたいケースです。.worktreeincludeが処理されない以上、.envやローカル証明書のコピーもこのフックの責任範囲に入ります。gitベースのままWorktreeCreateを使わずに、.worktreeinclude側でファイルコピーを完結させられるなら、そちらの方が構成はシンプルです。
よくあるつまずき
WorktreeCreateの標準出力にログを混ぜる: 進捗ログをechoで標準出力に流すと、Claude Codeが最後の行をworktreeパスと誤認して失敗します。ログは必ずstderrへ.worktreeincludeが効くと思い込む:WorktreeCreateを設定した時点でgitの既定動作は丸ごと置き換わり、.envなどのコピーはフックスクリプトの責任になりますWorktreeRemoveだけ設定してディレクトリが残る: exit codeが0以外で、かつディレクトリがまだ存在していると削除は失敗扱いです。削除コマンド自体が正しく走っているか、権限エラーが出ていないかを先に確認します- 相対パスやシンボリックリンク経由のパスをそのまま返す: v2.1.216以降、リポジトリ外へ誘導しうるパスは拒否されます。正規化済みの絶対パスを返す実装にしておくと安全です
systemMessageで進捗を通知しようとする: 両イベントともこのフィールドは読み捨てられます。通知が必要ならSlack Webhookなどをフックスクリプト内から直接叩きます
まとめ
WorktreeCreate/WorktreeRemoveは、Claude Codeのworktree隔離をgit以外のバージョン管理システムへ丸ごと差し替えるための入口です。WorktreeCreateは成功可否とパスの有無で結果が決まり、WorktreeRemoveはexit codeとディレクトリの残存確認で結果が決まるという、標準の許可/拒否モデルとは違う契約を持っています。SVNやPerforceの管理下でClaude Codeの並列セッションを回したいチームにとっては、この2つのフックが唯一の統合ポイントです。
導入するなら、まずWorktreeCreateのログをstderrへ分離すること、そしてWorktreeRemoveを必ず対で用意することの2点を先に固めておくと、パスの誤認識やディレクトリの残存といった典型的なつまずきを避けられます。
よくある質問
WorktreeCreateを設定しなくてもWorktreeRemoveだけ使えますか
公式ドキュメントが明確に説明しているのは、gitベースのworktreeではgit worktree removeによる自動クリーンアップが標準で走り、WorktreeCreateで非gitのVCSに差し替えた場合は対応するWorktreeRemoveを書かないとディレクトリが残る、という組み合わせです。それ以外の構成での挙動は公式ドキュメントの記載からは判断できません。
バックグラウンドセッションでもこのフックは発火しますか
発火します。バックグラウンドセッションもClaude Codeが独自のworktreeに隔離する対象で、削除時には保存済みのworktreeパスに対する検証を経てからWorktreeRemoveが実行されます。ただし、まだファイルが残っているworktreeを削除しようとした場合は、agent viewで削除を確認したときだけフックが実行され、確認しない場合はclaude rmがセッションとworktreeをそのまま保持します。
matcherで特定のセッションだけに絞り込めますか
絞り込めません。WorktreeCreateとWorktreeRemoveはどちらもmatcher非対応で、常に発火します。条件分岐が必要な場合は、標準入力のJSON(nameやworktree_path)をフックスクリプト側で見て処理を振り分けます。