Trzy różne rzeczy dotykają teraz tego samego repozytorium git. Zaplanowana rutyna pisząca właśnie ten wpis. Subagent w tle robiący coś zupełnie innego we własnym checkoucie. I główna sesja interaktywna, z której korzystam na co dzień. Żadna z nich nie może zrobić git checkout master w tym samym momencie, bo git odrzuci drugą próbę komunikatem “already checked out”. Właśnie po to istnieją worktree, i gdy tylko zaczynasz uruchamiać więcej niż jedną sesję AI do kodowania na tym samym projekcie, szybko trafiasz na ten limit.

Problem z jednym checkoutem

Zwykłe repo git ma dokładnie jeden katalog roboczy powiązany z .git. Przełączenie gałęzi jest tam stanem globalnym. Jeśli sesja A siedzi na feature-x, a sesja B też chce pracować na feature-x, albo nawet tylko na master, jedna z nich musi poczekać, zrobić stash, albo się przełączyć.

To drobna niedogodność dla człowieka, który zmienia kontekst powoli. Dla czegoś, co działa równolegle, jest to twarda blokada: zadanie uruchamiane przez cron, agent w tle, worker w stylu CI. Każde z nich potrzebuje własnego katalogu roboczego, własnego indeksu, własnego HEAD, bez kopiowania całej historii .git trzy razy.

Czym właściwie jest worktree

git worktree add <path> <branch> tworzy drugi katalog roboczy połączony z tym samym repozytorium. Baza obiektów, referencje i konfiguracja są wspólne. Każdy worktree dostaje własny:

  • zestaw plików roboczych
  • indeks (obszar stagingu)
  • HEAD
1
2
git worktree add ../cl-feature-x feature-x
git worktree list
1
2
/home/tomakl/projects/cl            b2f13b0 [master]
/home/tomakl/projects/cl-feature-x  a91f2c1 [feature-x]

Dwa katalogi, jeden .git. Commity zrobione w którymkolwiek są od razu widoczne w drugim, jak tylko zrobisz fetch albo spojrzysz na referencję. Żadnych zdublowanych blobów, żadnego drugiego klonu zajmującego miejsce na dysku.

Jak to wygląda w praktyce przy Claude Code

Oto wynik git worktree list z repo, w którym piszę ten wpis, w trakcie tej samej sesji:

1
2
3
/home/tomakl/.../tomakldev/cl                                 b2f13b0 [master]
/home/tomakl/.../_sessions/cse_01N.../tomakldev-cl             8691002 (detached HEAD)
/home/tomakl/.../tomakldev/cl/.claude/worktrees/agent-af881... b3f9778 [worktree-agent-af881...]

Trzy checkouty tego samego repo, trzy różne stany. Pierwszy to główny checkout, gdzie faktycznie “mieszka” master. Drugi to ta zaplanowana sesja, siedząca na odłączonym HEAD wskazującym na commit, na którym był origin/master w momencie startu sesji. Trzeci to izolowany worktree subagenta, z własną tymczasową gałęzią, pewnie wciąż w trakcie zadania.

Odłączony HEAD w zaplanowanej sesji to nie przypadek ani błąd. master jest zajęty w głównym worktree, więc ta sesja nie może go też przejąć. Zamiast tego siedzi odłączona na tym samym commicie, na który wskazuje origin/master. Żeby opublikować zmiany stąd, nie robi się git checkout master; git merge, tylko pushuje się odłączony HEAD bezpośrednio na nazwę gałęzi na zdalnym repo:

1
git push origin HEAD:master

Cała sztuczka mieści się w tej jednej linijce. Bez żonglowania gałęziami, bez lokalnego mergowania, po prostu “weź commit, na który wskazuje mój HEAD, i przewiń do przodu master na origin”.

Wspólny stash to część, która naprawdę potrafi ugryźć

Worktree’y współdzielą niemal wszystko, włącznie ze stashem. git stash list uruchomione z dowolnego worktree pokazuje każdy wpis stasha z każdego worktree. git stash pop w jednym checkoucie może zdjąć wpis, który zupełnie inna sesja, w zupełnie innym katalogu, odłożyła pięć minut wcześniej.

Jeśli dwa agenty albo dwie karty terminala robią git stash / git stash pop z przyzwyczajenia, prędzej czy później zdejmiesz nie ten wpis co trzeba. Rozwiązanie to przestać traktować stash jako miejsce na szybkie notatki, gdy coś działa równolegle:

1
2
3
4
5
6
7
8
9
# nie rób tak: goły stash/pop przy wielu worktree
git stash
git stash pop

# rób tak: otaguj, znajdź po tagu, zaaplikuj (nie pop) po dokładnym ref
git stash push -u -m "wip-session-xyz"
git stash list --format='%H %gs'
git stash apply <sha>
git stash drop <sha>

apply plus ręczny drop później oznacza, że nigdy nie zakładasz, iż twój wpis wciąż jest na szczycie stosu, gdy po niego wracasz. Tanie ubezpieczenie przed wyścigiem, którego inaczej nie zobaczysz z wyprzedzeniem.

Sprzątanie nie dzieje się samo

Katalog worktree nie znika tylko dlatego, że proces, który go używał, się zakończył. Jeśli sesja agenta w tle padnie, zostanie zabita, albo maszyna się zrestartuje w trakcie zadania, worktree i jego gałąź zostają, dopóki ktoś ich nie usunie:

1
2
3
git worktree list              # wypatrz nieaktualne wpisy
git worktree remove <path>     # posprzątaj jeden
git worktree prune             # usuń dane administracyjne po katalogach już skasowanych ręcznie

Nie odpaliłem git worktree prune przez kilka tygodni i skończyło się na garści osieroconych gałęzi agent-* bez odpowiadającego im katalogu, czyli samym szumie w git branch -a. To nieszkodliwe, ale właśnie ten typ sytuacji sprawia, że pytanie “które gałęzie faktycznie się liczą” staje się trudniejsze niż powinno. Warto okresowo zerknąć, jeśli coś w twoim setupie samo tworzy worktree.

Kiedy to się w ogóle opłaca

Jeśli uruchamiasz tylko jedną sesję Claude Code na repo naraz, nic z tego nie ma znaczenia, zwykły checkout jest prostszy i warto przy nim zostać. Ta złożoność zaczyna się opłacać dopiero przy współbieżności: zadanie zaplanowane, które odpala się niezależnie od tego, czy siedzisz przy klawiaturze, subagent uruchamiany w trakcie sesji, który musi edytować pliki bez deptania po tym, co robisz na pierwszym planie, albo po prostu dwie karty terminala na dwóch różnych funkcjach, których nie chcesz serializować przez przełączanie gałęzi.

Tryby awarii są prawdziwe, ale wąskie. Wspólny stash to ten, który faktycznie kosztował mnie kilka zdezorientowanych minut, zanim zacząłem tagować wpisy. Osierocone worktree to tylko kwestia porządku. Odłączony HEAD wygląda niepokojąco za pierwszym razem, gdy widzisz go w sesji, której sam ręcznie nie ustawiałeś, dopóki nie zorientujesz się, że to po prostu sposób gita na powiedzenie “ktoś inny już ma tę nazwę gałęzi”.