About

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だけの状態がある。

~/repo main worktree .git/ the one real repository objects/ shared refs/ (branches, tags, stash) shared worktrees/feature-x/ HEAD · index · gitdir per worktree app/ ... checked-out files ~/repo-feature-x linked worktree .git a text file, not a folder gitdir: ~/repo/.git/ worktrees/feature-x no symlink, no second .git/ app/ ... a real second copy

公式ドキュメント(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 から気軽に聞いてほしい。

動作フロー

1

`git worktree add ../path -b branch` → 新しいフォルダにファイルをチェックアウトし、その.gitファイルにgitdirを一行書く

2

gitはその一行をたどって、メインの.git/worktrees/<name>/のHEADとindexを使う

3

objectsとrefs(ブランチ・stash)は共有 — コミットがすぐ見える

4

`git worktree remove ../path` で片付け。rm -rfで消したなら `git worktree prune`

メリット

  • ✓ cloneよりディスクが少なく、fetchは一回
  • ✓ 同じブランチの二重チェックアウトをgitが止める

デメリット

  • ✗ .env・master.key・node_modulesはついてこない
  • ✗ stashが共有される — 別セッションのstashをpopしかねない

ユースケース

Claude CodeなどAIエージェントのセッションごとに作業場所を分離 PRレビュー用の別チェックアウト 長いCI・ビルドの間に別ブランチの作業
← git stashを正しく使う Portlessとは?localhostのポート番号をmyapp.localhostの名前に置き換える仕組み →