git worktreeはシンボリックリンクでもcloneでもない — .gitひとつに作業フォルダが複数
Claude Codeのセッションを並べるなら、セッションごとにworktreeをひとつ
worktreeを何か月も使っていたのに、これがシンボリックリンクなのか、cloneのように丸ごとコピーしたものなのか、ぼんやりしていた。中を開けてみたら、どちらでもなかった。作業ファイルは本物のコピーで、リポジトリ(.git)はひとつを共有している。 これが分かると、Claude Codeのセッションを複数並べても互いに踏まない分け方がすぐ見える。
.gitはフォルダではなくファイル
git worktree add ../repo-feature-x -b feature-x を打って、新しいフォルダの .git を開いた。
$ cat ../repo-feature-x/.git
gitdir: /path/to/repo/.git/worktrees/repo-feature-x
$ file ../repo-feature-x/.git
../repo-feature-x/.git: ASCII text
リンクではなく、一行だけのテキストファイルだ。gitはこの行を読んで、メインリポジトリの .git/worktrees/repo-feature-x/ を見に行く。そこに HEAD、index、gitdir など、このworktreeだけの状態がある。
公式ドキュメント(git help worktree のDETAILSとREFS)のルールは単純だ。HEAD のようなpseudo refはworktreeごと、refs/ の下(ブランチ・タグ・stash)は共有。だから片方でコミットすると、もう片方の git log feature-x にすぐ出る。fetchも一回で済む。
リンク・worktree・cloneを並べる
| シンボリックリンク | worktree | clone | |
|---|---|---|---|
| 作業ファイル | 同じファイル | 別(コピー) | 別(コピー) |
| objects・refs | - | 共有 | それぞれ |
| HEAD・index | - | それぞれ | それぞれ |
| 片方のコミットが見えるか | - | すぐ見える | push・fetchが必要 |
このリポジトリで測ると(2026-10-19、du -sh)、作業ファイルが461MB、.git が438MBだった。worktreeを一つ足すと461MB増え、cloneを一つ足すと .git も含めて900MB近く増える。
同じブランチは二か所で開けない
$ git worktree add ../dup feature-x
fatal: 'feature-x' is already checked out at '.../repo-feature-x'
最初は面倒な制約だと思った。並列セッションを回してみると、むしろありがたい。二つのセッションが同じブランチを触る事故を、gitが先に止めてくれる。
フォルダを rm -rf で消すと、記録は .git/worktrees/ に残る。git worktree list に prunable と出て、そのブランチはまだチェックアウト中扱いなので他の場所で開けない。git worktree prune を一回打てば解ける。最初から git worktree remove で消すほうがいい。
Claude Codeの並列セッションはセッションごとにworktree
Claude Codeのセッションを複数、ひとつの作業フォルダで動かすと、実際にこうなる。互いの未コミットの変更を上書きする。片方が git checkout すると、もう片方の作業が別のブランチに混ざる。
だからルールはひとつにした。作業ひとつ = worktreeひとつ。 置き場所はリポジトリ内の .claude/worktrees/ で、.gitignore に入れてある。Claude Codeの EnterWorktree も同じ場所に作るので、人が開いたものとエージェントが開いたものが一か所に集まる。この記事も、五つのブログを五つのセッションが同時に直している最中に、そのうちのひとつのworktreeで書いた。
手で開くときは「なければ作る、あれば入る」関数を置いている。タブを開いて wt 1 → claude で始められる。
wt() { # wt 1 → <repo>/.claude/worktrees/wt-1
local top dir
top=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
dir="$top/.claude/worktrees/wt-$1"
if [[ ! -d $dir ]]; then
git -C "$top" worktree prune
git -C "$top" fetch -q origin main
git -C "$top" worktree add -q --no-track -b "wt/$1" "$dir" origin/main
cp "$top/.env" "$dir/" 2>/dev/null || true
fi
cd "$dir"
}
--git-common-dir でメインリポジトリを探すので、worktreeの中から呼んでも同じ場所に行く。--no-track を付けると、新しいブランチのupstreamが origin/main にならない。作業が決まったら git branch -m で名前を変える。
ついてこないもの二つ
新しいworktreeはgitが管理するファイルしかチェックアウトしない。.env、config/master.key、node_modules/、SQLiteのDBはない。上の関数が .env をコピーしているのはそのためだ。
stashは共有される。refs/stash も refs/ の下にあるからだ。あるセッションが git stash pop すると、別のセッションのstashを取り出しかねない。worktreeの中ではstashではなく一時的なWIPコミットで逃がしている。
worktreeかcloneか
コードを直してPRまで出す作業ならworktreeで足りる。開発サーバーを立てて画面を見る作業は、cloneを別に置く。ポート・DB・node_modules が最初から独立していて、docker-composeがマウントするのもたいていメインのチェックアウトひとつだけだからだ。
ひとつのリポジトリに複数のエージェントをつないで動かしていて、どこで絡まるのか分からないなら、https://www.negabaro.com/contact から気軽に聞いてほしい。
動作フロー
`git worktree add ../path -b branch` → 新しいフォルダにファイルをチェックアウトし、その.gitファイルにgitdirを一行書く
gitはその一行をたどって、メインの.git/worktrees/<name>/のHEADとindexを使う
objectsとrefs(ブランチ・stash)は共有 — コミットがすぐ見える
`git worktree remove ../path` で片付け。rm -rfで消したなら `git worktree prune`
メリット
- ✓ cloneよりディスクが少なく、fetchは一回
- ✓ 同じブランチの二重チェックアウトをgitが止める
デメリット
- ✗ .env・master.key・node_modulesはついてこない
- ✗ stashが共有される — 別セッションのstashをpopしかねない