- AI ⚙️
Claude Code×git worktree並列開発|設定例と衝突対策表
Claude Code の並列開発とは、セッションごとに git worktree(同じリポジトリの別の作業フォルダ)を割り当て、複数の Claude に別々のブランチで同時に作業させる進め方です。 claude --worktree <名前> と打つだけで、.claude/worktrees/<名前>/ に作業フォルダが、worktree-<名前> という名前でブランチが作られ、その中でセッションが始まります。あるセッションの編集が別のセッションのファイルに触れることはありません。
ただし、worktree が分けてくれるのはファイルとブランチだけです。 ポート番号、ローカルのデータベース、コンテナの名前は分かれないので、そのまま並べると取り合いになります。この記事では、公式ドキュメントから起こした起動方法ごとの早見表と版ごとの挙動差の表、.worktreeinclude・worktree.baseRef・isolation: worktree・WorktreeCreate フックのコピペ用一式、worktree の名前からポート番号と DB 名を決めるスクリプト、worktree を3本作ってマージ競合を解消するまでの git の実行結果を載せました。
確認環境:Ubuntu 24.04/git 2.43.0/Python 3.11.15/bash 5.2.21/jq 1.7。Claude Code の仕様は公式ドキュメント(code.claude.com/docs)を 2026年10月7日に開いて確認し、git の操作とスクリプトはこの環境で実行しました。出力中のパスは /home/dev/taskboard に置き換えて表示しています。
Claude Codeのworktree早見表
| 起動方法 | 作成先 | ブランチ | 分岐元 | 終了時の掃除 |
|---|---|---|---|---|
claude -(- も同じ) |
. |
worktree- |
origin/ |
変更なしは自動削除、ありは確認 |
claude -(名前を省略) |
自動で付いた名前(例 bright-) |
worktree- |
origin/ |
同上 |
claude - |
. |
— | origin の pull/ |
同上 |
claude - |
. |
worktree- |
origin/ |
自動では消えない。git worktree remove |
セッション中に「worktree で作業して」(Enter ツール) |
— | — | — | — |
サブエージェントの isolation: worktree |
一時的な worktree | — | - と同じ |
変更なしで完了すると自動削除 |
/ |
サブエージェントごとの worktree | — | — | 各サブエージェントが PR を開く |
git worktree add(手動) |
自分で指定 | 自分で指定 | 自分で指定 | git worktree remove |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Claude Code×git worktree並列開発|設定例と衝突対策表」
「—」は公式ページに明記がない項目です。分岐元の origin/HEAD はリポジトリの既定ブランチのことで、設定 worktree.baseRef を "head" にすると、いま開いているローカルの HEAD から分岐するようになります。出典:https://code.claude.com/docs/ja/worktrees 、https://code.claude.com/docs/ja/best-practices (2026年10月7日確認)
コピペ用の設定一式
並列開発を始める前にリポジトリへ入れておくファイルは5つです。 どれも数行で、一度入れればチーム全員の worktree に効きます。
| ファイル | 役割 | コミット |
|---|---|---|
. |
worktree の中身をメイン側で未追跡として出さない | する |
. |
. など追跡していないファイルを新しい worktree にコピー |
する |
. |
分岐元(worktree.)やフックの設定 |
する |
. |
isolation: worktree を付けたサブエージェント |
する |
tools/ |
worktree ごとのポート番号・DB 名を決める(後述) | する |
.gitignoreへの追記
.claude/worktrees/
.wt-env
1行目は公式ページのヒントにある設定です。2行目は、後で紹介するスクリプトが worktree ごとに作るファイルです。
.worktreeinclude
worktree は新しいチェックアウトなので、.env のように git で追跡していないファイルは入っていません。プロジェクトのルートに .worktreeinclude を置くと、Claude Code が worktree を作るときに自動でコピーします。次は公式ページに載っている例です。
.env
.env.local
config/secrets.json
書き方は .gitignore と同じ構文です。コピーされるのは「パターンに一致し、かつ gitignore されているファイル」だけなので、追跡しているファイルが二重になることはありません。--worktree で作る worktree、サブエージェントの worktree、デスクトップアプリの並列セッションに適用されます。
settings.jsonのworktree.baseRef
{
"worktree": {
"baseRef": "head"
}
}
baseRef が受け付ける値は "fresh" と "head" の2つだけで、任意のブランチ名やコミットは指定できません。
| 値 | 分岐元 | 向いている場面 |
|---|---|---|
指定なし/"fresh" |
既定ブランチ origin/ |
リモートと同じきれいな状態から、独立した作業を始める |
"head" |
ローカルの HEAD |
まだ push していないコミットや、作業中の機能ブランチの続きを分担する |
セッションがすでに worktree の中で動いているとき、"head" はメインのチェックアウトではなく、その worktree の HEAD を指します。違いを git のコマンドで再現すると次のようになります。メイン側に push していないコミットが1つある状態で、origin/HEAD と HEAD のそれぞれから worktree を作りました。
$ git log --oneline -1 main
793253c 作業中:まだ push していないコミット
$ git log --oneline -1 origin/HEAD
3c598c2 Merge worktree-ui(API は 50件、画面表示は UI_PAGE_SIZE=10 に分離)
$ git worktree add -q -b worktree-fresh .claude/worktrees/fresh origin/HEAD && git -C .claude/worktrees/fresh log --oneline -1
3c598c2 Merge worktree-ui(API は 50件、画面表示は UI_PAGE_SIZE=10 に分離)
$ git worktree add -q -b worktree-head .claude/worktrees/head HEAD && git -C .claude/worktrees/head log --oneline -1
793253c 作業中:まだ push していないコミット
既定のままだと、手元でコミットしただけの変更は新しい worktree に入りません。 「さっきコミットした土台の上で分担させたい」ときは、先に push するか、baseRef を "head" にします。
isolation: worktreeを付けたサブエージェント
.claude/agents/parallel-implementer.md
---
name: parallel-implementer
description: 互いに独立したファイル群の実装を、専用の worktree の中で行う。複数の実装を並列に進めるときに使う
tools: Read, Grep, Glob, Edit, Write, Bash
isolation: worktree
---
あなたは、割り当てられた1つのタスクだけを実装する担当です。
- 変更してよいのは、依頼文に書かれたディレクトリの中だけです。それ以外のファイルを変える必要が出たら、変更せずに理由を報告してください。
- サーバーやテストを起動するときは、先に `eval "$(python3 tools/wt_env.py)"` を実行し、`$PORT` と `$DB_NAME` を使ってください。番号や名前を直接書かないでください。
- 作業が終わったら、テストを実行し、変更をコミットしてください。
- 最後に、変更したファイルの一覧、実行したテストのコマンドと結果、ブランチ名を報告してください。
フロントマターに isolation: worktree を書くと、このサブエージェントは呼ばれるたびに一時的な worktree を受け取り、変更なしで完了した場合は worktree が自動で削除されます。分岐元は --worktree と同じ規則です。その場かぎりで使うなら、定義ファイルを作らずに「エージェント用に worktree を使って」と指示する方法も公式ページに載っています。出典:https://code.claude.com/docs/ja/sub-agents
WorktreeCreateフック
WorktreeCreate フックを設定すると、worktree の作り方そのものを自分のスクリプトに置き換えられます。公式ページの例は、git 以外のバージョン管理(SVN)で作業コピーを作るものです。
{
"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\"'"
}
]
}
]
}
}
約束は3つです。フックは標準入力の JSON から worktree の名前(.name)を受け取ること、作成したディレクトリのパスを標準出力に出すこと、途中のメッセージは標準エラーに出すこと。0 以外の終了コードを返すと worktree の作成は中止されます。このフックを使うと既定の git の処理は丸ごと置き換わり、.worktreeinclude も処理されなくなるので、ローカルの設定ファイルはスクリプトの中でコピーします。
同じ約束で、git の worktree を作りつつ .env のコピーとポート番号の割り当てまで行うスクリプトを書くと、次のようになります。
.claude/settings.json
{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/worktree-create.sh"
}
]
}
]
}
}
.claude/hooks/worktree-create.sh
#!/bin/bash
# worktree-create.sh : WorktreeCreate フック用。worktree を作り、ローカル設定を写し、パスを標準出力に出す
set -euo pipefail
NAME=$(jq -r .name) # 標準入力の JSON から worktree 名を読む
ROOT=$(git rev-parse --show-toplevel)
DIR="$ROOT/.claude/worktrees/$NAME"
{
git worktree add -b "worktree-$NAME" "$DIR" origin/HEAD
# フックを使うと .worktreeinclude は処理されないので、ここでコピーする
for f in .env .env.local; do
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$DIR/$f"; fi
done
# worktree ごとのポート番号と DB 名を決めて .wt-env に保存する
(cd "$DIR" && python3 tools/wt_env.py)
} >&2 # 途中の出力はすべて標準エラーへ
echo "$DIR" # 標準出力には作成したパスだけを出す
Claude Code が渡すのと同じ形の JSON を標準入力に流して、スクリプト単体の動作を確かめました。標準出力にはパスだけが出て、作られた worktree には .env と .wt-env が入っています。
$ echo '{"name":"search"}' | .claude/hooks/worktree-create.sh 2>/dev/null; echo "終了コード: $?"
/home/dev/taskboard/.claude/worktrees/search
終了コード: 0
$ ls -A .claude/worktrees/search
.claude
.env
.git
.gitignore
.worktreeinclude
.wt-env
docs
src
tools
$ cat .claude/worktrees/search/.wt-env
export WT_NAME=search
export PORT=10441
export DB_NAME=taskboard_search
export CONTAINER_PREFIX=taskboard-search
対になる WorktreeRemove フックは、セッションやサブエージェントの終了時に worktree が削除されるときに発火します。WorktreeCreate と WorktreeRemove はどちらも matcher に対応しておらず、発生するたびに必ず実行されます。出典:https://code.claude.com/docs/ja/hooks-guide 、https://code.claude.com/docs/ja/hooks
終了時の掃除と、名前を再利用したときの動き
worktree のセッションを終了したときの動きは、変更の有無で3つに分かれます。
| 終了時の状態 | Claude Code の動き |
|---|---|
| 未コミットの変更・未追跡ファイル・新しいコミットがどれもない | worktree とブランチを自動で削除。セッションに名前を付けている場合は確認が出る |
| 未コミットの変更・未追跡ファイル・新しいコミットのいずれかがある | 保持するか削除するかの確認が出る。削除を選ぶとコミットも含めて破棄される |
-(非対話)で - を使った |
自動では掃除されない。git worktree remove で削除する |
サブエージェントやバックグラウンドセッション用に作られた worktree は別枠で、設定 cleanupPeriodDays より古く、未コミットの変更・未追跡ファイル・未 push のコミットがないものが自動で削除されます。--worktree で自分が作った worktree は、この定期的な掃除の対象になりません。エージェントが動いている間、Claude Code はその worktree に git worktree lock をかけ、完了すると解除します。
すでにディレクトリがある名前で claude --worktree api をもう一度実行すると、その worktree が再開されます。v2.1.208 以降は、次の3つがすべて当てはまる場合に、以前の先端のまま再開するのではなく、現在の分岐元にリセットされます。
- 未コミットの変更も未追跡ファイルもない
- Claude Code が作成したブランチの上にいる
- 一度もコミットしていない、または、プルリクエストがマージされてリモートのブランチが削除されている
版ごとの挙動差の表
公式ページの本文に書かれている版ごとの違いを抜き出しました。手元の版は claude --version で確認できます。
| 版 | 変わったこと | それより前の挙動 |
|---|---|---|
| v2.1.198 | worktree に入る・出るときに、セッションの記録がそのディレクトリの保存先へ移る(- がそこで見つける) |
— |
| v2.1.200 | メイン側でプロジェクトスコープに入れたプラグインが、同じリポジトリの worktree でも読み込まれる(入れ直し不要) | — |
| v2.1.203 | isolation: worktree のサブエージェントで、作業ディレクトリがメイン側に解決されるコマンドはエラーになる |
メイン側で実行できていた |
| v2.1.205 | 起動時に worktree に入れないと、パスを含むエラーを出してコード 1 で終了 | クラッシュ。- では約30秒止まってからコード 0 で終了 |
| v2.1.206 | . の外にある既存の worktree に入る前に承認を求める |
承認なしで入れた |
| v2.1.208 | 24時間以内にフェッチしていなければ、origin/ を更新してから分岐(上限5秒。失敗時は手元の参照を使う) |
手元に残っていた origin/ を使用 |
| v2.1.208 | 再利用した名前の worktree を、条件を満たせば現在の分岐元にリセット | 常に以前の先端のまま再開 |
| v2.1.211 | . は、worktree ではメインのチェックアウトのルートのものを使う |
起動したディレクトリに保持 |
出典:https://code.claude.com/docs/ja/worktrees 、https://code.claude.com/docs/ja/sub-agents 、https://code.claude.com/docs/ja/settings (2026年10月7日確認)
分岐元が思ったより前のコミットだったときは、版と origin/HEAD の2つを見ます。 v2.1.208 より前の版では、最後に git fetch した時点の origin/HEAD から分岐します。また、リモートを設定していないリポジトリや、origin/HEAD が手元になくフェッチもできない場合は、現在のローカルの HEAD から分岐します。
初めてのディレクトリで --worktree を対話的に使うときは、先にそのディレクトリで claude を1回実行して、ワークスペースの信頼ダイアログを受け入れておきます。受け入れ前だと --worktree はエラーで終了します。
worktreeで分かれるもの・分かれないもの
並列開発でつまずく原因の多くは、「分かれていると思っていたものが共有だった」ことです。 先に表で押さえておきます。
| 対象 | 分かれるか | 対策 |
|---|---|---|
| 作業フォルダのファイル | 分かれる | — |
チェックアウトしているブランチと HEAD |
分かれる(同じブランチは2か所で開けない) | worktree ごとに別ブランチ |
| ステージの状態(インデックス) | 分かれる | — |
追跡していないファイル(. など) |
新しい worktree には無い | . |
| インストール済みの依存、仮想環境 | 新しい worktree には無い | worktree ごとに初期化 |
gitignore した CLAUDE. |
作成した worktree にだけある | ホームからインポート |
| コミット履歴・ブランチ一覧・リモート | 共有 | — |
| ポート番号 | 共有(マシンで1つ) | 名前から番号を決める |
| ローカルのデータベース | 共有 | worktree ごとに DB 名を変える |
| コンテナの名前 | 共有 | 名前に worktree 名を入れる |
| ホーム配下のビルドキャッシュ | 共有 | 同時に書き換える処理を避ける |
| Claude Code の自動メモリ | 共有(同じリポジトリの worktree 全体) | — |
. |
共有(メイン側のルートを使用) | — |
| プロジェクトスコープのプラグイン | 共有(v2.1.200 以降) | — |
同じブランチを2か所で開こうとすると、git が止めてくれます。
$ git worktree add .claude/worktrees/again worktree-api; echo "終了コード: $?"
Preparing worktree (checking out 'worktree-api')
fatal: 'worktree-api' is already used by worktree at '/home/dev/taskboard/.claude/worktrees/api'
終了コード: 128
個人用の指示を全 worktree で使いたいときは、公式ドキュメントのとおり、CLAUDE.md にホームディレクトリのファイルをインポートする1行を書きます。
# Individual Preferences
- @~/.claude/my-project-instructions.md
依存のインストールについて、公式ページは「新しい worktree ごとに開発環境を初期化する」よう案内しています。出典:https://code.claude.com/docs/ja/memory 、https://code.claude.com/docs/ja/worktrees
worktree名からポート番号とDB名を決めるスクリプト
ポート番号と DB 名を人が割り振ると、worktree が増えるたびに表を更新する手間が生まれます。worktree の名前から機械的に決めれば、同じ worktree はいつも同じ番号になり、重なったときだけ自動でずれます。
tools/wt_env.py
#!/usr/bin/env python3
"""worktree の名前から、ポート番号・DB 名・コンテナ名の接頭辞を決めて export 文を出す。
一度決めた値は worktree 直下の .wt-env に保存し、2回目からは同じ値を返す。
使い方(worktree の中で):
eval "$(python3 tools/wt_env.py)"
eval "$(python3 tools/wt_env.py --base 3000 --span 1000 --app taskboard)"
"""
import argparse
import os
import re
import socket
import subprocess
import zlib
from pathlib import Path
def git(*args: str) -> str:
return subprocess.run(["git", *args], capture_output=True, text=True, check=True).stdout.strip()
def worktree_name() -> str:
"""メインのチェックアウトなら main、追加した worktree ならそのフォルダ名を返す。"""
top = Path(git("rev-parse", "--show-toplevel"))
git_dir = Path(git("rev-parse", "--absolute-git-dir"))
common = Path(git("rev-parse", "--path-format=absolute", "--git-common-dir"))
return "main" if git_dir == common else top.name
def is_free(port: int) -> bool:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(("127.0.0.1", port))
return True
except OSError:
return False
def pick_port(name: str, base: int, span: int) -> int:
"""名前のハッシュで開始位置を決め、使用中なら次の番号へずらす。"""
start = zlib.crc32(name.encode("utf-8")) % span
for i in range(span):
port = base + (start + i) % span
if is_free(port):
return port
raise SystemExit(f"{base}〜{base + span - 1} に空きポートがありません")
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--base", type=int, default=int(os.environ.get("WT_PORT_BASE", "3000")),
help="ポート範囲の先頭(環境変数 WT_PORT_BASE でも指定可)")
ap.add_argument("--span", type=int, default=int(os.environ.get("WT_PORT_SPAN", "1000")),
help="ポート範囲の個数(環境変数 WT_PORT_SPAN でも指定可)")
ap.add_argument("--app", default=None, help="アプリ名(省略時はリポジトリのフォルダ名)")
args = ap.parse_args()
top = Path(git("rev-parse", "--show-toplevel"))
saved = top / ".wt-env"
if saved.exists(): # 決定済みなら同じ値を返す(サーバー起動中でも番号が変わらない)
print(saved.read_text(encoding="utf-8"), end="")
return
name = worktree_name()
common = Path(git("rev-parse", "--path-format=absolute", "--git-common-dir"))
app = args.app or common.parent.name
slug = re.sub(r"[^a-z0-9]+", "_", name.lower()).strip("_")[:30] or "wt"
app_slug = re.sub(r"[^a-z0-9]+", "_", app.lower()).strip("_")
text = (
f"export WT_NAME={name}\n"
f"export PORT={pick_port(name, args.base, args.span)}\n"
f"export DB_NAME={app_slug}_{slug}\n"
f"export CONTAINER_PREFIX={app_slug}-{slug.replace('_', '-')}\n"
)
saved.write_text(text, encoding="utf-8")
print(text, end="")
if __name__ == "__main__":
main()
仕組みは次の3点です。
- 名前のハッシュ(CRC32)をポート範囲の個数で割った余りを開始位置にする
- その番号が使用中なら、空いている番号が見つかるまで1つずつずらす
- 決めた値を worktree 直下の
.wt-envに保存し、2回目からは同じ値を返す
メイン側と3本の worktree で実行した結果です。この記事では、ポート範囲を 10440〜10449 の10個に絞って実行しています(export WT_PORT_BASE=10440 WT_PORT_SPAN=10)。既定の範囲は 3000〜3999 です。
$ python3 tools/wt_env.py
export WT_NAME=main
export PORT=10446
export DB_NAME=taskboard_main
export CONTAINER_PREFIX=taskboard-main
$ (cd .claude/worktrees/api && python3 tools/wt_env.py)
export WT_NAME=api
export PORT=10449
export DB_NAME=taskboard_api
export CONTAINER_PREFIX=taskboard-api
$ (cd .claude/worktrees/ui && python3 tools/wt_env.py)
export WT_NAME=ui
export PORT=10440
export DB_NAME=taskboard_ui
export CONTAINER_PREFIX=taskboard-ui
$ (cd .claude/worktrees/fix-login-123 && python3 tools/wt_env.py)
export WT_NAME=fix-login-123
export PORT=10447
export DB_NAME=taskboard_fix_login_123
export CONTAINER_PREFIX=taskboard-fix-login-123
使うときは、worktree の中で eval に渡してから、サーバーやテストを起動します。
$ eval "$(python3 tools/wt_env.py --base 10440 --span 10)"; echo $PORT $DB_NAME
10449 taskboard_api
$ python3 -m http.server 10449 --bind 127.0.0.1 # ui の worktree から同じ番号で起動した場合
OSError: [Errno 98] Address already in use
$ python3 tools/wt_env.py --base 10440 --span 10 | grep PORT # api のサーバー起動中にもう一度実行
export PORT=10449
2行目のコマンドは、番号を直接書いて別の worktree から起動した場合の結果で、Address already in use で止まります。番号を直接書かず $PORT を使う、という1つの約束で、この取り合いを避けられます。 3つ目のコマンドは、自分のサーバーが動いている間に実行しても .wt-env の値が返ることを示しています。
決めた値の使い方の例です。
eval "$(python3 tools/wt_env.py)"
createdb "$DB_NAME" # PostgreSQL:worktree 専用の DB を作る(成功時は何も表示されない)
export DATABASE_URL="postgres://localhost/$DB_NAME"
export COMPOSE_PROJECT_NAME="$CONTAINER_PREFIX" # Docker Compose:プロジェクト名を worktree ごとに分ける
python3 -m http.server "$PORT" --bind 127.0.0.1 # 開発サーバーは $PORT で起動
createdb と dropdb は PostgreSQL 16.15 で実行して確かめました。Docker Compose のプロジェクト名は、公式ドキュメントによると -p フラグ、環境変数 COMPOSE_PROJECT_NAME、Compose ファイルの name:、Compose ファイルのあるディレクトリ名の順に決まり、使える文字は英小文字・数字・ハイフン・アンダースコア(先頭は英小文字か数字)です。既定ではディレクトリ名が使われるので、Compose ファイルが worktree の直下にある場合、.claude/worktrees/api ではプロジェクト名が api になります。アプリ名を含めた名前にそろえたいときは、上のように環境変数で指定します。出典:https://docs.docker.com/compose/how-tos/project-name/ (2026年10月7日確認)
Claude Codeのセッションに自動で渡す
Claude が実行する Bash コマンドにこの値を毎回渡したいときは、SessionStart フックで CLAUDE_ENV_FILE に書き込みます。公式ガイドが direnv の例で示している書き方と同じ形で、Claude Code はこのファイルを各 Bash コマンドの前に読み込みます。
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'cd \"$(jq -r .cwd)\" && python3 tools/wt_env.py > \"$CLAUDE_ENV_FILE\"'"
}
]
}
]
}
}
フックの入力 JSON に入っている cwd(イベントが起きたときの作業ディレクトリ)へ移動してから、スクリプトを実行しています。同じコマンドに cwd 入りの JSON を流すと、CLAUDE_ENV_FILE に指定したファイルには次の内容が書き込まれました。
export WT_NAME=ui
export PORT=10440
export DB_NAME=taskboard_ui
export CONTAINER_PREFIX=taskboard-ui
シェルだけの最小版
Python を使わず、名前から番号を決めるだけなら3行で書けます。空きの確認と保存はしません。
#!/bin/bash
# worktree のフォルダ名から 3000〜3999 のポート番号を決める(空き確認なしの最小版)
NAME=$(basename "$(git rev-parse --show-toplevel)")
SUM=$(printf '%s' "$NAME" | cksum | cut -d ' ' -f 1)
echo "export PORT=$((3000 + SUM % 1000))"
$ ./wt_port.sh
export PORT=3169
gitで実演:worktreeを3本作り、競合を解消してマージする
Claude Code が作るのと同じ配置(.claude/worktrees/<名前>/、ブランチ worktree-<名前>、分岐元 origin/HEAD)を、git のコマンドで手で作って、並列作業からマージまでを通します。題材は、設定ファイル src/settings.py と処理 src/tasks.py を持つ小さなリポジトリです。
3本作る
$ git worktree add -b worktree-api .claude/worktrees/api origin/HEAD
Preparing worktree (new branch 'worktree-api')
branch 'worktree-api' set up to track 'origin/main'.
HEAD is now at 28fc9c5 初期コミット
$ git worktree add -b worktree-ui .claude/worktrees/ui origin/HEAD
Preparing worktree (new branch 'worktree-ui')
branch 'worktree-ui' set up to track 'origin/main'.
HEAD is now at 28fc9c5 初期コミット
$ git worktree add -b worktree-docs .claude/worktrees/docs origin/HEAD
Preparing worktree (new branch 'worktree-docs')
branch 'worktree-docs' set up to track 'origin/main'.
HEAD is now at 28fc9c5 初期コミット
$ git worktree list
/home/dev/taskboard 28fc9c5 [main]
/home/dev/taskboard/.claude/worktrees/api 28fc9c5 [worktree-api]
/home/dev/taskboard/.claude/worktrees/docs 28fc9c5 [worktree-docs]
/home/dev/taskboard/.claude/worktrees/ui 28fc9c5 [worktree-ui]
$ ls -A .claude/worktrees/api
.git
.gitignore
docs
src
$ ls -A .
.claude
.env
.git
.gitignore
docs
src
メイン側にある .env が、worktree 側には無いことが分かります。.worktreeinclude が埋めるのはこの差です。
それぞれで作業してコミットする
3本の worktree で、次の変更をコミットしました。api と ui は、同じ src/settings.py の同じ行(PAGE_SIZE)を別の値に変えています。
| worktree | 変更 |
|---|---|
| api | PAGE_ を 20 → 50、src/ に関数を追加 |
| ui | PAGE_ を 20 → 10、THEME の行を追加 |
| docs | docs/ に追記、docs/ を追加 |
$ git branch -vv
* main 28fc9c5 [origin/main] 初期コミット
+ worktree-api a5b4442 (/home/dev/taskboard/.claude/worktrees/api) [origin/main: ahead 1] API: 1ページ50件にする
+ worktree-docs db6b4c4 (/home/dev/taskboard/.claude/worktrees/docs) [origin/main: ahead 1] docs: 並列開発の手順を追加
+ worktree-ui b24bb0e (/home/dev/taskboard/.claude/worktrees/ui) [origin/main: ahead 1] UI: 1ページ10件にしてテーマ設定を追加
マージの前に、重なりを調べる
マージを始める前に、どのブランチ同士が同じファイルを触っているかを調べると、順番を決めやすくなります。 git diff --name-only main...<ブランチ> で各ブランチが変えたファイルを出し、comm -12 で共通部分を取ります。
$ git diff --name-only main...worktree-api
src/settings.py
src/tasks.py
$ git diff --name-only main...worktree-ui
src/settings.py
$ git diff --name-only main...worktree-docs
docs/README.md
docs/parallel.md
$ comm -12 <(git diff --name-only main...worktree-api | sort) <(git diff --name-only main...worktree-ui | sort)
src/settings.py
git merge-tree --write-tree を使うと、作業フォルダを変えずに、2つのブランチをマージしたら競合するかどうかまで分かります。競合があると終了コードが 1 になります。
$ git merge-tree --write-tree --name-only worktree-api worktree-ui; echo "終了コード: $?"
0773d2c0a54a73688341351f90369f78018dc518
src/settings.py
Auto-merging src/settings.py
CONFLICT (content): Merge conflict in src/settings.py
終了コード: 1
$ git merge-tree --write-tree --name-only worktree-api worktree-docs; echo "終了コード: $?"
5ee594585d30bc3a590989280be9a53e05b32b2c
終了コード: 0
重ならないものから順にマージする
docs はどれとも重ならないので最初に、次に api、最後に ui をマージします。
$ git merge --no-ff -m "Merge worktree-docs" worktree-docs
Merge made by the 'ort' strategy.
docs/README.md | 1 +
docs/parallel.md | 1 +
2 files changed, 2 insertions(+)
create mode 100644 docs/parallel.md
$ git merge --no-ff -m "Merge worktree-api" worktree-api
Merge made by the 'ort' strategy.
src/settings.py | 2 +-
src/tasks.py | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
$ git merge --no-ff worktree-ui; echo "終了コード: $?"
Auto-merging src/settings.py
CONFLICT (content): Merge conflict in src/settings.py
Automatic merge failed; fix conflicts and then commit the result.
終了コード: 1
$ git status --short
UU src/settings.py
$ cat src/settings.py
APP_NAME = "taskboard"
<<<<<<< HEAD
PAGE_SIZE = 50
=======
PAGE_SIZE = 10
>>>>>>> worktree-ui
TIMEOUT_SEC = 30
THEME = "light"
THEME の行は自動で取り込まれ、同じ行を変えた PAGE_SIZE だけが競合として残りました。
競合を解消する
api は「API が返す件数」、ui は「画面に並べる件数」を変えたかったので、どちらか一方を選ぶのではなく、設定を2つに分けて両方の意図を残します。src/settings.py を次の内容に直しました。
APP_NAME = "taskboard"
PAGE_SIZE = 50
UI_PAGE_SIZE = 10
TIMEOUT_SEC = 30
THEME = "light"
$ git add src/settings.py
$ git commit -m "Merge worktree-ui(API は 50件、画面表示は UI_PAGE_SIZE=10 に分離)"
[main 3c598c2] Merge worktree-ui(API は 50件、画面表示は UI_PAGE_SIZE=10 に分離)
$ git log --oneline --graph
* 3c598c2 Merge worktree-ui(API は 50件、画面表示は UI_PAGE_SIZE=10 に分離)
|\
| * b24bb0e UI: 1ページ10件にしてテーマ設定を追加
* | a687897 Merge worktree-api
|\ \
| * | a5b4442 API: 1ページ50件にする
| |/
* | 2964592 Merge worktree-docs
|\ \
| |/
|/|
| * db6b4c4 docs: 並列開発の手順を追加
|/
* 28fc9c5 初期コミット
$ python3 -c "import sys; sys.path.insert(0, \"src\"); import settings, tasks; print(settings.PAGE_SIZE, settings.UI_PAGE_SIZE, tasks.count_pages(120))"
50 10 3
片付ける
$ git worktree remove .claude/worktrees/docs
$ git worktree remove .claude/worktrees/api
$ echo memo > .claude/worktrees/ui/memo.txt; git worktree remove .claude/worktrees/ui; echo "終了コード: $?"
fatal: '.claude/worktrees/ui' contains modified or untracked files, use --force to delete it
終了コード: 128
$ git worktree remove --force .claude/worktrees/ui
$ git branch -d worktree-api worktree-ui worktree-docs
warning: not deleting branch 'worktree-api' that is not yet merged to
'refs/remotes/origin/main', even though it is merged to HEAD
error: the branch 'worktree-api' is not fully merged.
If you are sure you want to delete it, run 'git branch -D worktree-api'
…(省略)…
$ git push -q origin main 2>/dev/null; git branch -d worktree-api worktree-ui worktree-docs
Deleted branch worktree-api (was a5b4442).
Deleted branch worktree-ui (was b24bb0e).
Deleted branch worktree-docs (was db6b4c4).
$ git worktree list
/home/dev/taskboard 3c598c2 [main]
片付けで2つの確認が入りました。未追跡ファイルが残っている worktree は --force を付けるまで消えません。また、origin/HEAD から分岐したブランチは origin/main を追跡しているので、main を push する前に git branch -d を実行すると「まだマージされていない」と止められます。 push してからなら -d で安全に消せます。どちらも、作業を失わないための git の確認です。
並列にしてよい作業・分けないほうがよい作業
並列に向くのは、触るファイルが重ならず、1本ずつ単独でテストできる作業です。 迷ったら次の表に当てはめます。
| 作業の組み合わせ | 判断 | 進め方 |
|---|---|---|
| 別々のディレクトリの機能追加 | 並列にする | worktree を分け、最後に順にマージ |
| ドキュメントとコード | 並列にする | 重ならないので先にドキュメントからマージ |
| 実装と、そのレビュー | 並列にする | 書く側と見る側でセッションを分ける |
| 不具合の調査と機能開発 | 並列にする | 調査は変更なしで終われば worktree が自動で消える |
| 多数のファイルへの同じ変換 | 並列にする | / か、一覧を作って claude - で1件ずつ |
| 同じファイルの同じ箇所を触る2つの作業 | 順番にする | 土台になるほうを先にマージしてから次を始める |
| 共通の型・設定・ルーティングの変更と、それを使う実装 | 順番にする | 共通部分を先に入れ、base を使って続きを分担 |
| 連番のマイグレーションを足す2つの作業 | 順番にする | 番号を先に割り当てるか、片方を後から採番し直す |
| ロックファイルが変わる依存の追加を2つ | 順番にする | 依存の追加だけ先に1本でまとめる |
| 大きな名前変更・ディレクトリ移動と他の作業 | 順番にする | 名前変更を先に済ませ、他はその後に分岐 |
| 同じデータベースのテーブル定義を変える2つの作業 | 順番にする | DB を worktree ごとに分けても、定義の統合は1本ずつ |
マージ順の決め方
- 各ブランチが変えたファイルを出す。
git diff --name-only main...<ブランチ> - どれとも重ならないブランチを先にマージする。 今回の docs にあたります
- 重なるブランチ同士は、土台になるほうを先にする。 共通の設定や型を決めた側、他から呼ばれる側が先です
- 後からマージする側は、先に
mainを取り込んで競合を自分の worktree の中で解消する。 解消してテストを通してからマージすれば、mainが壊れた状態になる時間がありません - 1本マージするたびにテストを実行する。 まとめてマージすると、どの組み合わせで壊れたかが分かりにくくなります
手順4は、worktree の中で git merge main(または git rebase main)を実行し、競合の解消をその worktree を担当している Claude に任せる形にできます。経緯を知っているセッションが解消するので、どちらの意図も残しやすくなります。
並列の手段はworktreeだけではない
公式のベストプラクティスには、並列に進める手段が6つ挙げられています。worktree は「ファイルの編集を分ける」役割で、ほかの手段と組み合わせて使います。
| 手段 | 内容 |
|---|---|
| Worktrees | 分離された git チェックアウトで、個別の CLI セッションを動かす |
| クロスセッションメッセージング | 自分で動かしているセッション同士が、分かったことを渡し合う |
| デスクトップアプリ | 複数のローカルセッションを画面で管理。各セッションが独自の worktree を持つ |
| Web 上の Claude Code | クラウド上でセッションを動かす |
| エージェントビュー | バックグラウンドのセッションを1画面で監視する(研究プレビュー) |
| エージェントチーム | 共有タスクとメッセージで複数セッションを自動調整(実験的、既定で無効) |
同じページには、書く側とレビューする側のセッションを分ける Writer/Reviewer の進め方と、/batch <instruction> で変更を5〜30個のサブエージェントに分け、それぞれが独自の worktree で作業してプルリクエストを開く方法も載っています。出典:https://code.claude.com/docs/ja/best-practices
よくある質問
Q. worktree は何本まで増やしてよいですか? A. git の側に本数の決まりはなく、決め手は「自分が結果を確認できる本数」と、マシンのメモリ・ポートの余裕です。まず2〜3本で、マージ順の決め方とポートの割り当てに慣れるところから始めると進めやすくなります。
Q. .worktreeinclude に書いたのにコピーされません。
A. 対象のファイルが gitignore されているかを確認します。コピーされるのは、パターンに一致し、かつ gitignore されているファイルだけです。また、WorktreeCreate フックを設定している場合は .worktreeinclude が処理されないので、フックのスクリプトの中でコピーします。
Q. 新しい worktree に、さっきのコミットが入っていません。
A. 既定の分岐元は origin/HEAD なので、push していないコミットは入りません。push するか、worktree.baseRef を "head" にします。
Q. 終了したのに worktree が残っています。
A. 変更・未追跡ファイル・新しいコミットがあると確認のうえ保持され、-p で作ったものは自動では消えません。git worktree list で確認し、不要なら git worktree remove <パス> で削除します。未追跡ファイルが残っている場合は、中身を確かめてから --force を付けます。
まとめ
claude --worktree <名前>で、.claude/worktrees/<名前>/とブランチworktree-<名前>が作られる。 分岐元は既定ブランチorigin/HEAD- 手元のコミットから分担したいときは
worktree.baseRefを"head"にする。 受け付ける値は"fresh"と"head"だけ .envなどは.worktreeincludeでコピーする。 対象は gitignore されているファイルに限られる- 終了時の掃除は3通り。 変更なしは自動削除、変更ありは確認、
-pは手動でgit worktree remove - worktree が分けるのはファイルとブランチ。 ポート番号・DB・コンテナ名・自動メモリは共有なので、名前から決めるスクリプトで分ける
- マージの前に
git diff --name-onlyとgit merge-treeで重なりを調べ、重ならないものから順に入れる - 同じファイルの同じ箇所、連番のマイグレーション、ロックファイルを触る作業は順番に進める
参考・出典
確認日はすべて 2026年10月7日です。
- worktree を使用して並列セッションを実行する:https://code.claude.com/docs/ja/worktrees
- Claude Code のベストプラクティス:https://code.claude.com/docs/ja/best-practices
- カスタムサブエージェントの作成:https://code.claude.com/docs/ja/sub-agents
- hooks でアクションを自動化する:https://code.claude.com/docs/ja/hooks-guide
- Hooks リファレンス:https://code.claude.com/docs/ja/hooks
- Claude があなたのプロジェクトを記憶する方法:https://code.claude.com/docs/ja/memory
- 設定ファイルと優先順位:https://code.claude.com/docs/ja/settings
- Docker Compose のプロジェクト名:https://docs.docker.com/compose/how-tos/project-name/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Claude Code×git worktree並列開発|設定例と衝突対策表」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。