Montag, kurz nach acht. Zwei Agenten angeworfen: Einer soll den Login-Bug richten, der andere den Dark Mode einbauen. Beide im selben Verzeichnis, weil das eben da war. Fünf Minuten später sieht git status aus wie zwei Puzzles in einer Schachtel, die Tests sind rot, und keiner kann sagen, wer von beiden schuld ist. Das Problem ist nicht die Kolonne. Das Problem ist die Baustelle: zwei Arbeiter, eine Werkbank.
Git Worktrees geben jedem Arbeiter sein eigenes Zimmer: ein zweites Arbeitsverzeichnis neben dem Repo, mit eigenem Branch, eigenen Dateien, eigener Werkbank. Nur das .git bleibt eins. Es liegt im Keller, und alle Zimmer hängen am selben Hausanschluss. Bei dem Bild bleiben wir den ganzen Artikel über: Worktree heißt ab hier Zimmer, das geteilte .git ist der Keller.
Das Werkzeug ist alt und lag hier schon zweimal als Tipp im Regal, in Git für … Consultants und im CLI-Adventskalender: git. Nur galt das jemandem, der mal eben einen Hotfix dazwischenschiebt und danach wieder auszieht. Mit einer Kolonne aus Agenten gelten andere Regeln, und du bist der Vorarbeiter.
Das Zimmer selbst ist in zwei Minuten erklärt. Was hier drinsteht, sind die Sachen, die dir am ersten Tag mit zwei Agenten passieren und in keinem Cheatsheet stehen:
- In deinem Zimmer taucht ein Stash auf, den du nie gemacht hast.
- Du suchst zum dritten Mal einen freien Port raus und fragst dich, ob das jetzt immer so geht.
git worktree prunehat „aufgeräumt“, und in dem Zimmer saß noch einer.
Für alle drei gibt es eine Erklärung und einen Handgriff, der sie abstellt.
Ausblick: drei Schichten
Dieser Artikel ist die erste von drei Schichten. Heute richten wir die Baustelle ein, die nächsten beiden Teile bauen darauf auf, und am Ende tippst du einen Befehl, und der Einzug passiert von selbst.
| Schicht | Was dabei rauskommt |
|---|---|
| 1: heute | Warum deine Agenten eigene Zimmer brauchen, wie du die Baustelle dafür einrichtest, und welche Fallen still zuschnappen |
| 2: nächste Schicht | Der Einzug wird ein Skript, und du entscheidest, wer es aufruft: du, ein Wrapper oder Git selbst per Hook, und welche Haken dieser Hook hat |
| 3: die Schicht danach | Ab in den Keller: was Git beim Anlegen eines Zimmers wirklich anlegt, warum git gc dort nichts wegräumt, und wie Docker in jedes Zimmer eigene Leitungen legt |
Warum ein Agent anders arbeitet als du
Ein Agent arbeitet nicht mit starren Dateien, sondern mit einem Zustand. Er hat sich beim Reinkommen umgeguckt, sich ein Bild gemacht und plant seine nächsten Handgriffe darauf. Wechselst du währenddessen den Branch, ziehst du ihm die Leiter weg. Er schraubt oben weiter, an einem Stand, den es nicht mehr gibt.
Für dich ist stashen, wechseln, zurückwechseln ein normaler Handgriff. Ein laufender Agent kann das nicht, und das Unangenehme daran: er merkt es nicht. Keine Fehlermeldung, kein Stutzen. Er baut einfach Unsinn mit vollem Selbstvertrauen, und du siehst es erst, wenn du drüberguckst.
Ohne Worktrees hast du zwei Möglichkeiten, und beide kosten:
- Beide in dieselbe Bude. Fliesenleger und Elektriker gleichzeitig im Bad. Jeder steht dem anderen auf den Füßen, im
git statusliegt alles durcheinander, und beim Drübergucken sortierst du erst mal, wessen Kabel das ist. - Einer nach dem anderen. Erst der Elektriker, dann der Fliesenleger. Sauber, keine Frage. Nur steht das Bad dann zwei Wochen, und der zweite Agent, den du extra angeworfen hast, wartet im Flur. Meistens endet es damit, dass die kleine Sache nie anfängt.
Git Worktree ist die dritte Möglichkeit: ein Hausanschluss, mehrere Zimmer. Objekte, Branches und Remotes liegen weiter genau einmal im Keller, im geteilten .git. Jedes Zimmer bekommt einen eigenen HEAD und einen eigenen Index, also exakt den Zustand, den ein Agent für sich braucht. Der Handgriff dafür:
git worktree add ../projekt-wt/login-fix --no-track -b fix/login origin/main cd ../projekt-wt/login-fix # Agent starten, arbeiten lassen, PR aufmachen # ... # Arbeit erledigt, aufräumen git worktree remove ../projekt-wt/login-fix
Der Pförtner kommt gratis dazu: Git lässt denselben Branch nur in einem Verzeichnis auschecken. Zwei Agenten passen physisch nicht an dieselbe Werkbank.
Kolonne einweisen
Für den Agenten ist sein Zimmer ein ganz normales Projektverzeichnis, von den anderen weiß er nichts. Der Hausanschluss wird jedoch gemeinsam genutzt, und damit gehen ein paar seiner Handgriffe bis ins Nachbarzimmer durch.
Bei den gefährlichsten davon stellt sich Git von selbst davor:
$ git branch -D feat/dark-mode # der Branch von Agent B error: cannot delete branch 'feat/dark-mode' used by worktree at '/home/du/code/projekt-wt/dark-mode' $ git checkout feat/dark-mode fatal: 'feat/dark-mode' is already used by worktree at '/home/du/code/projekt-wt/dark-mode'
Zwei Dinge sind jedoch nicht abgesichert, und an beiden klemmst du dir im Agentenbetrieb die Finger:
- Der Stash ist die Werkzeugkiste im Flur. Legt Agent A was rein, liegt es oben drauf. Greift Agent B mit
git stash poprein, kriegt er das Oberste, und das kann die Arbeit vom Nachbarn sein. Agenten legen gern mal „zur Sicherheit“ was rein, bevor sie was Größeres umbauen. Taucht doch mal einer auf, verrät ihngit stash list:stash@{0}: On feat/dark-mode: zur Sicherheit. Am Branchnamen siehst du, aus welchem Zimmer er kommt. - Ein
git configohne den Zusatz--worktreewirkt wie der Hauptschalter. Dreht ein Agent in seinem Zimmer anuser.email, schreiben ab sofort alle Agenten ihre Commits mit dieser Identität. Den Zusatz gibt es übrigens erst, wenn du einmalgit config extensions.worktreeConfig truegesetzt hast. Vorher antwortet Git auf--worktreemitfatal:.
Hier hält Git dir nicht den Rücken frei, das machst du selbst: Bevor einer loslegt, weist du ihn ein. Nicht lang, aber deutlich: wo sein Abschnitt anfängt, wo er aufhört, und was er nicht anfasst. Sonst kommt der hilfsbereite Kumpel irgendwann auf die Idee, „mal kurz“ nebenan nach der Ursache zu gucken, und schraubt dort gleich einen Fix rein. Auf der Baustelle ist das der Satz vor Schichtbeginn. Hier ist es die Einweisung: die erste Nachricht an den Agenten, oder der Eintrag in der Instruktionsdatei des Zimmers, bei Claude Code etwa CLAUDE.local.md:
Du arbeitest im Worktree projekt-wt/login-fix auf dem Branch fix/login. Bleib in diesem Verzeichnis: keine Pfade nach ../ und nicht in andere Worktrees. Das .git ist mit anderen Sessions geteilt – deshalb kein git stash, kein git config, kein git worktree und kein Branch-Wechsel. Zwischenstände committest du auf fix/login, gepusht wird mit git push -u origin fix/login.
Fünf Zeilen, und jede davon verhindert genau einen der Unfälle von oben. Der letzte Satz ist der wichtigste: Ein Agent, der committen darf, hat keinen Grund mehr, was in die Kiste zu legen. Manche Tools ziehen diesen Zaun selbst, wenn sie das Zimmer anlegen, und blocken jeden Griff ins Hauptverzeichnis. Gut so, aber verlass dich nicht drauf: Die fünf Zeilen gelten für jeden Agenten, egal womit er läuft.
Baustelle einrichten
Bevor die Kolonne kommt, richtest du die Baustelle ein. Einmal, ordentlich. Die meisten Fallen weiter unten sind nämlich keine Git-Probleme, sondern Projekt-Probleme: Das Repo geht davon aus, dass es nur an einer Stelle steht. Vier Handgriffe, und die halbe Fallen-Tabelle geht dich nichts mehr an.
1. Leg das Zimmer neben das Repo, nicht rein.
Das Repo selbst ist die gute Stube, da wohnt main. Die Zimmer kommen daneben, sonst laufen dir Watcher, Tooling und find in den Abschnitt vom Nachbarn. Manche Agenten-Tools legen ihre Zimmer trotzdem ins Repo, in einen eigenen Unterordner. Dann gehört der in die .gitignore und in die Watcher-Excludes, mehr nicht: Port und Compose-Name hängen nur am Verzeichnisnamen und funktionieren dort genauso.
~/code/projekt/ # Haupt-Worktree, hier lebt main ~/code/projekt-wt/login-fix/ # ein Zimmer pro Task
2. Lass den Port sich selbst ausrechnen.
An der Stelle greifen die meisten zum Zettel und lassen es nach dem dritten Zimmer bleiben. Muss nicht sein: Der Verzeichnisname ist pro Zimmer eindeutig, daraus lässt sich eine Türnummer ableiten, ohne dass du irgendwo was einträgst.
// vite.config.ts — einmal ins Repo, gilt für jedes Zimmer
import { basename } from 'node:path'
const room = basename(process.cwd())
const port = 5300 + [...room].reduce((sum, c) => sum + c.charCodeAt(0), 0) % 100
export default defineConfig({
server: { port, strictPort: true },
})
Drei Dev-Server, kein einziger Handgriff pro Zimmer:
projekt/ → http://localhost:5367 projekt-wt/login-fix/ → http://localhost:5309 projekt-wt/dark-mode/ → http://localhost:5384
Und zwar dauerhaft: login-fix ist nach jedem Neustart wieder die 5309. Mit strictPort knallt es außerdem sofort, falls zwei Namen doch mal auf dieselbe Zahl fallen. Dann benennst du das Verzeichnis um, einmal, für immer.
Und Docker? Da lohnt sich eine Minute, weil die meisten hier ein Problem lösen, das sie gar nicht haben. Es gibt nämlich zwei verschiedene Ports, und nur einer davon geht dich überhaupt was an:
| Port | Pro Zimmer verschieden? | |
|---|---|---|
| Im Container | 5432 | Nein, nie; Postgres lauscht immer auf 5432 |
| Auf dem Host | 55466, 32770, … | Ja; existiert nur, damit du von außen reinkommst |
Compose legt in jedes Zimmer eigene Leitungen: eigenes Netz, eigene Container, eigene Volumes, und den Verzeichnisnamen hängt es an alles dran. Darin heißt die Datenbank db, und weil das Netz abgeschottet ist, zeigt db in jedem Zimmer auf den eigenen Container.
Zwei Fälle:
- Deine App läuft im Compose mit. Dann ist
postgres://postgres@db:5432/appin jedem Zimmer hartcodiert richtig, und jedes Zimmer redet mit seiner eigenen Datenbank. Kein Port-Mapping, keine Variable, kein Hook. Das Problem hast du nicht. - Deine App läuft auf dem Host (
npm run dev, der Normalfall bei Vite), nur die DB im Container. Jetzt gehst du über die Grundstücksgrenze und brauchst einen Host-Port pro Zimmer:
# docker-compose.yml
ports:
- "${DB_PORT:-5432}:5432" # Compose liest .env aus dem Zimmer
# projekt-wt/login-fix/.env — eine Zeile, einmal beim Einzug DB_PORT=55466
Genau diese eine Zeile ist der Grund, warum es Teil 2 gibt: Beim zweiten Zimmer tippst du sie, beim zehnten lässt du sie schreiben.
3. Wichtig: Der Bauplan
Leg eine .env.example ins Repo, nicht als Doku, sondern als Bauplan.
Sie beantwortet die Frage „was braucht ein frisches Zimmer“, ohne dass jemand seine echte .env rumreicht. Die echte .env steht in der .gitignore und bleibt in der guten Stube. Die Example ist der Filter: Was da nicht drinsteht, kommt auch in kein Zimmer.
4. Pack Migrationen und Seed in den Einzug, und bau sie so, dass sie zweimal laufen dürfen.
Denn jedes Docker-Zimmer startet mit einer leeren Datenbank. Nicht weil was kaputt ist: Compose hängt den Verzeichnisnamen an alles, was es anlegt: Container login-fix-db-1, Netz login-fix_default, und eben auch das Volume login-fix_dbdata. Das ist eine eigene Festplatte, und Postgres richtet sie frisch ein. Zwei Zimmer, zwei Kühlschränke: Der in der guten Stube ist voll, weil du ihn seit Wochen befüllst. Der im neuen Zimmer kommt leer vom Händler.
Genau das willst du: Agent A darf sein Schema umbauen, ohne dass Agent B es merkt. Der Preis ist, dass der Einzug die DB befüllen muss. Und weil du einen Einzug wiederholst (Zimmer neu gebaut, Setup nach einem Fehler nochmal angeworfen), darf der zweite Durchlauf weder knallen noch alles doppelt anlegen. Idempotent nennt sich das. Das beste Bild dafür ist ein Drehmomentschlüssel: Einmal macht es klick, danach kannst du ansetzen, so oft du willst – fester wird es nicht. Migrationen können das meist von Haus aus, ihr Tool führt Buch. Seeds oft nicht: Ein nacktes INSERT legt beim zweiten Mal Dubletten an, ein INSERT … ON CONFLICT DO NOTHING nicht.
Danach schrumpft der Einzug auf drei Zeilen zusammen, die du dem Agenten direkt in die Hand drückst:
git worktree add ../projekt-wt/login-fix --no-track -b fix/login origin/main cd ../projekt-wt/login-fix cp ../../projekt/.env.example .env && npm install && docker compose up -d
Wenn dir das beim dritten Mal zu blöd wird, nimmt dir Teil 2 das ab: ein Skript, und du entscheidest, wer es aufruft. Die Reihenfolge bleibt aber dieselbe: Erst die Baustelle einrichten, dann automatisieren. Ein Projekt mit hartverdrahteten Ports wird durch einen Hook nicht besser, nur schneller kaputt.
Baustellenordnung
- Ein Agent, ein Task, ein Zimmer. Zwei Aufgaben in einem Zimmer bringen genau das Chaos zurück, das wir eigentlich verhindern wollten – nur in einer sauberer beschrifteten Kiste.
- Immer von
origin/mainabzweigen, mit--no-track, nie von dem, was zufällig ausgecheckt ist. Sonst wandert dein halbfertiger Feature-Stand in den Diff des Agenten, und wer drüberguckt, darf raten, was zum Task gehört. - Zwei bis drei Zimmer, nicht acht. Die Grenze ist nicht Git und auch nicht dein Budget, sondern wie viele Diffs du heute noch lesen willst. Drei fertige, abgenommene PRs sind mehr wert als acht Berge unverstandener Änderungen.
- Grenzen in die Einweisung. Verzeichnis nicht verlassen, kein
git stash, keingit config. Kostet eine Zeile und spart dir fremde Nebenwirkungen. - Der Verzeichnisname ist der Taskname.
login-fixerklärt sich in drei Wochen noch selbst,wt2nicht. Und imworktree listsiehst du sofort, wer noch arbeitet. - Das Zimmer stirbt mit dem PR. Abgenommen heißt ausgezogen, und zwar in der Reihenfolge erst
docker compose down -v, danngit worktree remove, Git weiß von Docker nichts. Kommt Feedback, baust du es in zwanzig Sekunden neu. - Ports und Pfade aus Variablen mit Default. Alles Hartverdrahtete kollidiert, sobald der zweite Agent seinen Dev-Server anwirft.
Wo du dir die Finger klemmst
Die gute Nachricht: Das meiste knallt sofort, mit Fehlermeldung. Die schlechte: Ein paar Fallen siehst du nicht. Die zwei teuersten sind die leere Datenbank, bei der alles läuft, nur ohne Daten, und der Vite-Port, der auf 5174 ausweicht. Vite sagt das sogar, gleich in der ersten Zeile: Port 5173 is in use, trying another one.... Nur liest das keiner, der Agent nicht und du auch nicht, weil dein Port seit Wochen derselbe ist. Also testest du im Browser den Server vom Nachbarn. Beide Fallen merkst du erst, wenn ein Agent schon zwanzig Minuten am falschen Problem schraubt.
| Falle | Was passiert | Werkzeug |
|---|---|---|
Repo mit cp -r statt Worktree | Beide Agenten auf main, Git merkt nichts davon. Fällt erst beim Push auf: ! [rejected] main -> main | Worktree; der Branch-Pförtner greift nur da |
node_modules fehlt im neuen Zimmer | Agent startet, nichts läuft, er fängt an zu „reparieren“ | Install pro Zimmer. pnpm nimmt dir den Schritt nicht ab, macht ihn aber billig: Der Store liegt schon da, das zweite Zimmer verlinkt nur noch. Das klappt, solange Store und Zimmer auf derselben Platte liegen |
| Leere Datenbank im Docker-Zimmer | Compose isoliert Volumes pro Verzeichnis. Der Agent jagt Fehler, die außerhalb seines Tasks liegen | Migration und Seed ins Setup, idempotent |
.env fehlt oder ist zu vollständig | Ohne startet nichts. Mit allem drin sitzt ein Agent mit Schreibrechten auf Prod-Credentials | Gefiltert kopieren, nicht die Vollausstattung |
| Zwei Dev-Server, ein Port | Vite weicht auf 5174 aus und sagt das genau einmal, im Startbanner. Danach weiß keiner mehr, welcher Agent wo läuft | Port aus dem Verzeichnisnamen rechnen (Punkt 2) und strictPort setzen, damit es knallt statt ausweicht. Vite liest PORT nicht |
| Zwei Datenbanken, ein fester Host-Port | Docker weicht nicht höflich aus: Bind for 0.0.0.0:5432 failed: port is already allocated | "${DB_PORT:-5432}:5432" oder den Host-Port ganz weglassen, wenn die App im selben Compose-Netz läuft |
Nackter git push aus dem Zimmer | Ohne --no-track hängt fix/login an origin/main. Mit push.default = tracking landet git push direkt auf main: fix/login -> main | --no-track beim Einzug, pushen nur mit git push -u origin fix/login |
| Agent stasht „zur Sicherheit“ | Gemeinsame Werkzeugkiste – sein stash pop kann die Arbeit des Nachbarzimmers auspacken | In der Einweisung verbieten, committen lassen. Claude Code blockt im Auto Mode zwar stash drop und andere Wegwerf-Befehle (Must-Haves Juni ’26), stash pop aber nicht |
Agent setzt git config | Gilt sofort in allen Zimmern | In der Einweisung verbieten. Oder einmalig git config extensions.worktreeConfig true, danach gilt git config --worktree nur im eigenen Zimmer |
HEAD@{1} aus dem falschen Terminal | Jedes Zimmer führt sein eigenes Logbuch (Reflog), die Referenz zeigt überall auf einen anderen Stand | Reflog-Rettung nur dort, wo der Unfall passiert ist |
git worktree remove beim Ausziehen | Liegt im Zimmer nur Ignoriertes, hält Git es für sauber: .env, Coverage und lokale Configs verschwinden ohne Rückfrage und ohne --force. Bei nicht versionierten Dateien hätte Git verweigert | Vorher reingucken, wenn im Zimmer was Lokales entstanden ist. Für ein Wegwerf-Zimmer ist genau das gewünscht |
Zimmer mit mv verschieben, dann prune | Das Kellerfach ist weg:fatal: not a git repository: (null), repair kommt zu spät. Branch und Commits liegen aber unberührt im Keller, nur was nicht committet war, steht als loser Ordner da | git worktree move benutzen, nach einem mv erst git worktree repair <neuer-pfad>, nie prune. Ist es passiert, baut git worktree add <pfad> <branch> das Zimmer neu. Langlebige Zimmer mit git worktree lock --reason "…" schützen |
| Zwölf vergessene Zimmer | Jeder Zimmer-HEAD ist ein GC-Root, ein Nagel, an dem Objekte hängen bleiben; git gc bekommt nichts weg, das Repo wächst und wächst | Aufräumen ist hier keine Kosmetik |
Abnahme
Halb sechs. Beide sind durch, beide wollen ihren Abschnitt abgenommen haben und nach main. Jeder hat seine Tests grün, aber grün gegen das main, das morgens beim Einzug da war. Sobald der Erste merget, hat der Zweite gegen einen Stand geprüft, den es gar nicht mehr gibt. Keiner hat gepfuscht, und main ist trotzdem hin.
Toby nennt das ein Stale Green: ein grünes Häkchen, ausgestellt auf einen Stand, den es nicht mehr gibt.
Der Handgriff dagegen ist alt: Vor dem PR holst du main ins Zimmer, git fetch && git rebase origin/main, und lässt die Tests noch einmal laufen. Das darf der Agent selbst, wenn es in seiner Einweisung steht. Nur garantiert es leider nichts: Zwischen seinem Rebase und dem finalen Merge kann der Nachbar die Tür schon wieder eingerannt haben.
Abnahme heißt deshalb: Nicht der, der gebaut hat, sagt, dass es hält. Aus dem Zimmer heraus kommt der Agent an main sowieso nicht ran. main ist in der guten Stube ausgecheckt – dem Hauptverzeichnis. Derselbe Pförtner, der die Branches der Nachbarn schützt, blockt auch Befehle wie git checkout main oder git branch -f main. Was der Agent aber theoretisch könnte: rübergehen und selbst mergen, oder per git push origin fix/login:main direkt ins Remote pushen. Beides funktioniert, bedeutet aber auch: Er nimmt sich selbst ab. Genau das dreht Tobias‘ Artikel Merge Queue für KI-Agenten um: main geschützt, der Agent stellt seinen PR vor die Tür, und die Merge Queue prüft jeden Eintrag noch einmal gegen den Stand, der jetzt da ist. Der Artikel fängt genau da an, wo unsere Zimmertür aufhört.
Dass das mit einer ganzen Kolonne trägt, zeigt der Multi-Modell-Workflow mit Dynamic Agents: jeder Implementierer in seinem eigenen Worktree, bis zu vier parallel, und der Planer nimmt sie einzeln ab. Das ist die zweite Sorte Parallelität, die die Claude Code Must-Haves Juni ’26 von der ersten trennen: Nicht mehr du teilst die Zimmer zu. Du bittest den Haupt-Agenten darum, und er legt sie für seine eigenen Subagenten an. Das Prinzip bleibt dasselbe.
Und die Rechnung? Drei Agenten parallel heißt nicht dreimal so früh Feierabend, sondern dreimal so schnell am Weekly-Limit. Warum das für Power-User die echte „Tax“ ist, steht in Die Desk Tax ist ein Mythos, dort am Beispiel von drei parallelen Instanzen.
Nächste Schicht: git worktree add, und erst mal ’n Kaffee
Werkzeug in den Kasten, Zimmer abgeschlossen: git worktree remove, und morgen baust du es neu. Der Einzug bleibt dabei vorerst Handarbeit. Drei Zeilen, aber eben jedes Mal. Das muss nicht sein: Das Setup passt perfekt in ein Skript, für das es drei verschiedene Auslöser gibt. Der bequemste Auslöser ist Git selbst: git worktree add feuert einen Hook ab, und der läuft bereits im neuen Zimmer.
Nur hat dieser naheliegende Hook so seine Tücken. Die erste: Er läuft bei jedem Checkout mit, auch wenn du nur eine Datei zurückholst. Die zweite: Die eine Zeile, die das verhindert, ist in manchen Repos selbst kaputt, ohne dass du es merkst. Welche Zeile das ist, und ob du den Hook überhaupt willst oder lieber ein Skript, das du in der Hand hast, klärt der kommende Teil meiner kleinen Serie.
Zweimal gemessen, einmal gesägt: Alle Befehle, Fehlermeldungen und Messwerte in diesem Artikel stammen aus echten Repos, nichts davon ist angenommen. Nur die Pfade sind anonymisiert.


Schreibe einen Kommentar