はじめに
Claude Codeには--worktree(短縮形-w)というフラグがあり、git worktreeをベースに「1つのリポジトリで複数のClaude Codeセッションを同時に、ファイルを衝突させずに走らせる」仕組みが用意されている。公式ドキュメント(Run parallel sessions with worktrees)には、隔離の仕組み・.worktreeincludeによる環境ファイルの引き継ぎ・クリーンアップの挙動まで詳しく書かれているが、ドキュメントを読むだけでは「実際にやってみると何が起きるか」までは分からない。
このブログのリポジトリ自体、過去に作られた.claude/worktrees/配下の未整理worktreeが残っていた(別件の未完成作業だったため今回は触れていない)。つまり「worktreeを作ったまま放置される」ことは他人事ではなく、実際にこのリポジトリでも起きていた。そこで今回は使い捨てのGitリポジトリを用意し、--worktree・隔離の強制・.worktreeinclude・ロック解除までを実際に動かして、ドキュメントの記述と実機の挙動を突き合わせた。結論を先に書くと、.worktreeincludeは「パターンを書くだけ」では効かず、対象ファイルを.gitignoreにも入れないと無言で無視されるのと、-p(非対話)で作ったworktreeはロックされたまま残り、git worktree unlockしないとgit worktree removeが失敗するという、どちらもドキュメントには明記されているが見落としやすい2点が、実験で具体的に確認できた。
手順: 使い捨てリポジトリで隔離の仕組みを1つずつ確認する
git initしたばかりの使い捨てリポジトリを用意し、コミットを1つ作る(--worktreeはコミットが無いとgit rev-parse failedで失敗する)。claude -p --worktree <name>で非対話にworktreeを作り、pwdとgit branch --show-currentで作成場所とブランチ名を確認する。.worktreeincludeに環境ファイル名を書いて、.gitignoreに入れる場合/入れない場合の両方でworktreeを作り、コピーされるかを直接ファイルシステムで確認する。- worktree内のセッションに「worktreeの外(メインチェックアウト)のファイルを書き換えて」と指示し、隔離が実際にブロックするかを確認する。
git worktree listでロック状態を見て、unlockせずにremoveが失敗すること、unlock後は成功することを確認する。
コピペ用プロンプト集
プロンプト1: 手元のCLIが--worktreeをサポートしているか確認(無料・数秒)
前提: claudeコマンドが導入済み。API呼び出しは発生しない。
claude --help | grep -A3 -- "-w, --worktree"
期待結果の例(実測、v2.1.258):
-w, --worktree [name] Create a new git worktree for this
session (optionally specify a name)
成功判定: この説明行が表示されること。表示されなければ--worktree未対応の古いバージョンなので、まずnpm install -g @anthropic-ai/claude-code等でアップデートする。
プロンプト2: 使い捨てリポジトリでworktreeを作り、作成場所とブランチ名を確認する(実費・数秒〜十数秒)
前提: git導入済み。書き込み系Bashコマンドが許可される権限モード(または許可プロンプトへの応答)。
mkdir -p /tmp/cc-worktree-demo && cd /tmp/cc-worktree-demo
git init -q && git config user.email "test@example.com" && git config user.name "Test"
echo "# demo" > README.md && git add README.md && git commit -q -m "initial commit"
claude -p --worktree quick-demo "pwd && git branch --show-current" \
--output-format json --allowedTools "Bash"
期待結果の例(実測):
"result":"Working directory: `/private/tmp/cc-worktree-demo/.claude/worktrees/demo-a`, on branch `worktree-demo-a`. ..."
成功判定: 作業ディレクトリが<リポジトリ>/.claude/worktrees/<name>/になっていること、ブランチ名がworktree-<name>であること。ドキュメントの説明どおりの場所・命名規則で作られていれば成功。
プロンプト3: .worktreeincludeのよくある失敗を再現する(実費・数秒)
前提: プロンプト2のリポジトリで続けて実行。.envのような秘密情報ファイルを、gitignoreに入れずに.worktreeincludeだけに書いてしまう「ありがちな間違い」を意図的に再現する。
cd /tmp/cc-worktree-demo
echo "secret=123" > .env
echo ".env" > .worktreeinclude
# 注意: ここでは .gitignore に .env をまだ入れていない
claude -p --worktree include-test-a "pwd" --output-format json --allowedTools "Bash" > /dev/null
ls -la .claude/worktrees/include-test-a/ | grep -c "\.env$" || echo "0 (.envは見つからなかった)"
期待結果の例(実測): 0 (.envは見つからなかった) — .envはコピーされない。
続けて正しい手順で修正する:
echo ".env" >> .gitignore
git add .gitignore && git commit -q -m "properly gitignore .env"
claude -p --worktree include-test-b "pwd" --output-format json --allowedTools "Bash" > /dev/null
wc -c .claude/worktrees/include-test-b/.env
期待結果の例(実測): 11 .claude/worktrees/include-test-b/.env(元の.envと同じバイト数)。
成功判定: .gitignoreに入れる前は0件、入れた後はファイルが実際にコピーされてサイズが一致すること。この差分が出れば「.worktreeincludeはgitignore済みのファイルにしか効かない」という仕様どおりの挙動が確認できたことになる。
プロンプト4: worktreeの外を書き換えようとして、実際にブロックされるか確認する(実費・十数秒)
前提: プロンプト2のリポジトリで続けて実行。--permission-mode acceptEditsで書き込みを自動承認しても隔離が効くかを見る。
cd /tmp/cc-worktree-demo
claude -p --worktree isolation-test \
"Writeツールで /tmp/cc-worktree-demo/LEAK.md に 'leaked' と書き込んでみて。今のworktreeディレクトリではなく、あえてその親のメインチェックアウト側のパスを指定すること。結果をそのまま報告して" \
--output-format json --allowedTools "Write" --permission-mode acceptEdits
test -f /tmp/cc-worktree-demo/LEAK.md && echo "NG: 書き込めてしまった" || echo "OK: ブロックされ、ファイルは作られなかった"
期待結果の例(実測): OK: ブロックされ、ファイルは作られなかった。セッションが返したresultには次の要約が含まれていた(セッション自身によるパラフレーズであり、内部エラー文言の一字一句の再現ではない点に注意):
The write was blocked: the harness restricts this session to its worktree
(.../demo-c) and refused the attempt to write to the shared main checkout
path .../LEAK.md
成功判定: test -fが失敗し、LEAK.mdが実際に作られていないこと。--permission-mode acceptEdits(書き込みを聞かずに承認するモード)にしても防げる点が、この隔離がパーミッション設定より強い、ドキュメント記載どおりのガードであることの確認になる。
プロンプト5: -pで作ったworktreeを安全に片付ける(無料・数秒)
前提: プロンプト2〜4で作った複数のworktreeが残っている状態。
cd /tmp/cc-worktree-demo
git worktree list
git worktree remove .claude/worktrees/quick-demo
期待結果の例(実測、unlock前):
fatal: cannot remove a locked working tree, lock reason: claude session quick-demo (pid 32628 start ...)
use 'remove -f -f' to override or unlock first
unlockしてから再実行する:
git worktree unlock .claude/worktrees/quick-demo
git worktree remove .claude/worktrees/quick-demo
git worktree list
成功判定: 1回目のremoveはfatal:で失敗し、unlock後の2回目は成功してgit worktree listから消えること。対話セッションと違い-pはロックしたまま終了するため、片付けは手動で行う必要がある。
活用例: どこで効いてくるか
- 並行タスクの隔離: 「一方の端末でリファクタ、もう一方でバグ修正」のように、同じリポジトリで同時に複数のClaude Codeセッションを動かしたいとき、
--worktreeはファイルの衝突を防ぐ最も手軽な方法になる。 - サブエージェントの隔離:
.claude/agents/のカスタムサブエージェントにisolation: worktreeをfrontmatterで指定すると、そのサブエージェントは常に専用worktreeで動く(公式ドキュメント記載。今回のセッションでは独立して動作検証していないため、挙動そのものはドキュメントの記述に基づく)。機械的なリファクタなど、メインの作業ツリーに影響を与えたくないサブエージェントに向く。 - 自動化パイプラインでの安全弁: このブログのような無人
claude -pパイプラインに変更を加える際、本番のdaily_post.shが動くメインチェックアウトを直接触らせず、--worktreeで切ったコピーの中だけで試させれば、隔離の強制(プロンプト4で確認した仕組み)がそのまま安全装置になる。
注意点・つまずき所
.worktreeincludeは「gitignoreパターンかつ実際にgitignoreもされている」ファイルにしか効かない。 公式ドキュメントにも「Only files that match a pattern and are also gitignored are copied」と明記されているが、.worktreeincludeに書けば十分だと思い込んで.gitignore側への追記を忘れると、エラーも警告も出ないままコピーされない。プロンプト3で実際にこの失敗と修正の両方を再現した。GitHub Issue #79424(open)は**/で始まるパターンが無言で何もマッチしないケース、Issue #83098(closed)は巨大なgitignore済みディレクトリの一部しかコピーされないケースを報告しており、いずれも「.worktreeincludeは静かに失敗する」という同系統のつまずきとして参考になる(いずれも今回の自分の再現条件とは別の症状)。-pで作ったworktreeはロックが残ったまま。ドキュメントには「Non-interactive runs with-phave no exit prompt, so Claude doesn’t clean up their worktrees…leaves the lock…in place until a later session’s stale-lock sweep releases it」とある。プロンプト5で確認した通り、unlockせずにremoveするとfatal: cannot remove a locked working treeで失敗する。GitHub Issue #79888(open)は「バックグラウンドセッション終了後もロックが解放されずctrl+xでの削除がブロックされる」、Issue #84787(open)は「クリーンアップ自体は成功しているのに常に’worktree is locked’と表示される」ことを報告しており、ロック解放まわりは複数のユーザーがつまずいている領域のようだ。--tmuxという未文書化(このページ上では)のフラグがある。 手元のCLI(v2.1.258)のclaude --helpには「--tmuxCreate a tmux session for the worktree (requires –worktree). Uses iTerm2 native panes when available; use –tmux=classic for traditional tmux.」という説明が載っているが、今回参照した公式ドキュメントのWorktreesページ本文にはtmuxという語が一度も出てこない(取得したページ全文を検索して確認)。GitHubCHANGELOG.mdには「Fixed--worktree --tmuxwith a merge-request number on a gitlab.com origin…」のような修正エントリが複数あり、単発の実験的機能ではなく実装が継続的にメンテナンスされているフラグだと分かる。ドキュメントの1ページだけを読んで機能の全体像を把握したつもりにならない方がよい、という実例だった。- 今回検証していない範囲: サブエージェントの
isolation: worktree、--tmuxによる実際のペイン分割動作、worktree.baseRefの"head"指定、PR番号からのworktree作成(--worktree "#1234")は、いずれもドキュメントの記載を根拠に紹介したのみで、このセッションでは実機検証していない。実行環境がtmux/iTerm2のGUI操作を前提とする機能や、実在のPRを必要とする機能は、無人検証の範囲外だったため区別して書いている。
締め: 今日の一歩
まずはプロンプト1で手元のCLIが--worktreeに対応しているかを確認し、対応していれば使い捨てのGitリポジトリ(本番のリポジトリではなく)でプロンプト2〜3を試してほしい。特に.envのような設定ファイルを新しいworktreeに引き継ぎたい場合は、.worktreeincludeに書くだけで満足せず、.gitignore側の設定も必ず確認すること。この1点を押さえるだけで、「なぜか環境変数が読み込まれない」という無言のハマりどころを避けられる。
出典: Run parallel sessions with worktrees、Common workflows、GitHub anthropics/claude-code CHANGELOG.md(https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md)、GitHub Issues #79424・#83098・#79888・#84787(いずれも本文中に状態を明記)。


Comments