Zwei Agenten, ein Arbeitsbaum — was schiefgeht, wenn parallele Claude-Code-Sessions dasselbe Git-Repository teilen

Eine Claude-Code-Session öffnet ein Git-Repository, in dem keine Änderung offen ist, arbeitet eine halbe Stunde und committet am Ende das, was sich geändert hat. Im Einzelbetrieb ist das korrekt. Sobald eine zweite Session oder ein Mensch im selben Repository arbeitet, kann derselbe Ablauf fremde Arbeit mit in den Commit ziehen, und Git warnt nicht davor.

Dieser Artikel erzählt fünf reale Fälle aus gut einer Woche, in der parallele Claude-Code-Sessions dasselbe Repository geteilt haben, teils untereinander, teils mit dem Maintainer. Drei der fünf Fälle waren Fehler des Agenten. Zwei davon hat der Agent selbst bemerkt und gemeldet, den dritten hat erst die Nachbar-Session entdeckt, deren Arbeit verschwunden war. Aus den Fällen ergeben sich Gegenmaßnahmen, und die naheliegendste davon ist genau die, die nicht hält.

Das Wichtigste vorab:

  • Die gemeinsame Wurzel: Eine Session sieht beim Start ein Repository ohne offene Änderungen. Taucht während ihres Laufs eine neue oder geänderte Datei auf, weil eine parallele Session oder der Mensch sie gerade erstellt oder geändert hat, sieht das für die Session genauso aus wie eine Datei, die ihr eigenes Werkzeug erzeugt hat. Sie behandelt die fremde Datei deshalb wie ihre eigene und committet sie mit oder räumt sie weg. Die Annahme „alles Neue stammt von mir“ wird nirgends widerlegt, weil Git für Änderungen im Arbeitsverzeichnis keinen Urheber kennt.
  • Der Index ist geteilter Zustand: Git sammelt Änderungen für den nächsten Commit in einer Vorstufe, dem Index, und git add legt sie dort ab. Arbeiten mehrere Sessions im selben Arbeitsverzeichnis, teilen sie sich auch dessen Index. Wer statt git add -A nur die eigene Datei mit git add <datei> hinzufügt, ist trotzdem nicht geschützt, weil git commit ohne Pfadangabe den kompletten Index committet, also auch das, was eine andere Session dort bereits abgelegt hat.
  • Zerstörende Operationen sind im Parallelbetrieb nie Aufräumen: Wer eine Datei auf die Vorversion zurücksetzt, einen Stand verwirft oder eine Datei löscht, trifft damit alles, was unter dem angegebenen Pfad liegt, und nicht nur das eigene Werk. Die Einschätzung „das war ich“ ist dabei eine Vermutung, die sich prüfen lässt, und keine Beobachtung.
  • Geteilte Zähler kollidieren: Vergeben zwei Sessions gleichzeitig die nächste fortlaufende Nummer, etwa für einen neuen Bug-Eintrag, dann lesen beide denselben Höchststand und vergeben dieselbe Nummer. Weichen anschließend beide höflich auf die nächste Nummer aus, kollidieren sie erneut. Eine Prüfung vorab allein verhindert das nicht, weil das Zeitfenster zwischen Lesen und Schreiben liegt.
  • Geteilt ist auch die Maschine: Parallele Sessions teilen nicht nur das Repository, sondern den Rechner, an dem der Mensch gleichzeitig arbeitet. Ein Agent, der für einen Lasttest 220 Endlosschleifen startet, sieht danach nur Exit-Codes, nicht die Oberfläche, die beim Menschen zwölf Minuten lang ruckelt. Diese Kosten haben keine Rückmeldeschleife zum Agenten, und bei mehreren Sessions kann der Mensch nicht einmal erkennen, welche sie verursacht.
  • Was hält: Gegen fremde Index-Einträge halten git commit --only <pfade>, ein Blick auf den Index in einem eigenen Schritt vor dem Commit und ein git show --stat danach. Für geteilte Zähler braucht es eine vorher festgelegte Tie-Break-Regel, also eine Vorfahrtsregel für den Gleichstand, die ohne Absprache entscheidet, welche Seite ausweicht, und eine Prüfung nach dem Schreiben, die Nummern-Liste und Dateibestand gegeneinander abgleicht, statt nur nach doppelten Nummern zu suchen. Gegen gleichzeitige Änderungen an derselben Datei schützt keines dieser Kommandos. Wo der Ablauf es zulässt, bekommt jede Session deshalb einen eigenen Git-Worktree.
  • Die Regel kam nach dem Schaden: Die Gegenmaßnahmen stehen seit dem zweiten Fall in einer Regel-Datei des Blog-Repositorys. Sie sind eine Folge der Fälle und nicht deren Voraussetzung.

Voraussetzung: Git-Grundkenntnisse werden vorausgesetzt, Claude-Code-Vorkenntnisse nicht. Die beiden tragenden Git-Begriffe, Arbeitsbaum und Index, erklärt der erste Abschnitt kurz. Die Fälle stammen aus zwei Repositories, dem privaten App-Projekt DI² und dem Repository dieses Blogs, alle zwischen dem 28. August und dem 4. September 2026.

Inhalt

Warum parallele Claude-Code-Sessions Fehler erzeugen, die es allein nicht gibt

Eine Coding-Agent-Session hat einen klaren Blick auf ihre Aufgabe und einen blinden Fleck bei ihrer Umgebung. Zwei Git-Begriffe kommen in diesem Artikel immer wieder vor und sollen deshalb kurz eingeordnet werden. Als Arbeitsbaum, englisch working tree, bezeichnet Git die Dateien, die auf der Platte liegen und die es gegen den letzten Commit vergleicht. Der Index ist die Vorstufe zum Commit: Mit git add legt man dort die Änderungen ab, die der nächste Commit enthalten soll. Die Session sieht den Arbeitsbaum beim Start, sie sieht die Ausgabe ihrer Kommandos, und sie sieht am Ende, welche Dateien sich geändert haben. Was sie nicht sieht, ist, ob in der Zwischenzeit jemand anderes denselben Arbeitsbaum benutzt hat. Git hält für Änderungen im Arbeitsbaum nicht fest, welcher Prozess oder welche Session sie verursacht hat. Im normalen Betrieb gehört zu jedem Arbeitsbaum ein eigener Index, und wer im selben Arbeitsbaum arbeitet, teilt sich mit allen anderen dort auch den Index. Eine zweite Session oder ein Mensch mit offenem Editor hinterlässt darin Spuren, die von den eigenen nicht zu unterscheiden sind.

Daraus folgt eine Annahme, die im Einzelbetrieb stimmt und im Parallelbetrieb falsch ist: „Der Arbeitsbaum war sauber, als ich anfing, also stammt alles Neue von mir.“ Ob sie zum Problem wird, hängt vom Zeitpunkt ab und nicht von der Sorgfalt der Session, und nichts davon fühlt sich für die Session anders an als der Normalfall.

Alle Fälle in diesem Artikel gehen auf dieselbe Ursache zurück. Eine Session denkt bei jedem Kommando nur an die Dateien, die sie selbst angefasst hat. Git und die Shell kennen diese Grenze nicht: git commit nimmt den ganzen Index, ein checkout oder ein Löschbefehl wirkt auf alles unter dem angegebenen Pfad, egal wer es geändert hat, und ein Lasttest belegt die ganze Maschine. Solange eine Session allein arbeitet, fällt das nicht auf, weil alles, was sich geändert hat, ohnehin von ihr stammt. Sobald eine zweite Session oder ein Mensch im selben Repository arbeitet, stimmt das nicht mehr, und die Session macht mit fremder Arbeit genau das, was sie mit ihrer eigenen tun würde. Daraus entstehen Fehler, die ohne einen zweiten Akteur im selben Arbeitsbaum oder auf derselben Maschine gar nicht möglich wären.

Die fünf Fälle unten sind in gut einer Woche im Alltag zweier Repositories aufgetreten, ohne dass jemand nach ihnen gesucht hätte. Mal arbeiteten zwei Sessions nebeneinander an verschiedenen Themen, mal arbeitete eine Session, während der Maintainer im selben Repository Dateien anfasste. Beides ist ein ganz normaler Betriebsmodus, sobald man mit mehr als einem Agenten arbeitet. Der übergeordnete Artikel Agentic Coding aus Anwender-Sicht ordnet den Parallelbetrieb in die Gesamterfahrung ein. Dieser Artikel behandelt nur ihn.

Fall 1: Fremde Arbeit mitgenommen

Im DI²-Projekt arbeiteten der Maintainer und eine Agenten-Session am selben Repository, zeitweise gleichzeitig. Die Session committete routinemäßig mit git add -A und schrieb dazu eine ausführliche Commit-Message, die eng an ihrem Thema blieb. Zwischen zwei ihrer Commits hatte der Maintainer an ganz anderer Stelle gearbeitet, nämlich an einer Testfall-Konvention, an einem Skill und an einem CI-Guard. Der nächste git add -A-Commit der Session nahm diese vier Dateien mit. Die Commit-Message handelt von einem Scroll-Bugfix. Von den rund hundert Zeilen fremder Konventions-Arbeit steht darin kein Wort.

Aufgefallen ist das nicht beim Commit, sondern Stunden später und nur durch Zufall. Beim Einarbeiten von Material in eine Artikel-Sammlung standen dort Zitate aus Repo-Dateien mit dem Vermerk, sie seien noch nicht committet und beim Verifizieren sei der Folge-Commit zu suchen. Die zitierten Abschnitte fanden sich auch, allerdings in einem Commit, dessen Message ein völlig anderes Thema trägt. Ohne diesen Abgleich der Belege wäre der Fall unentdeckt geblieben.

Ein Datenverlust ist das nicht, die Arbeit liegt vollständig im Repository. Der Schaden liegt in der Auffindbarkeit. Wer die Herkunft der Konventions-Änderung sucht, findet sie über die Commit-Message nicht mehr, sondern nur noch über eine Inhaltssuche mit git log -S. Dazu kommt, dass der Commit stillschweigend die Autorschaft für fremde Arbeit beansprucht, samt der Co-Authored-By-Zeile des Agenten. Die naheliegende Lehre lautet, nicht mehr pauschal zu stagen, sondern die eigenen Dateien einzeln zu benennen. Der zweite Fall zeigt, warum diese Lehre unvollständig ist.

Fall 2: Die naheliegende Gegenmaßnahme greift nicht

Einen Tag später arbeiteten im Repository dieses Blogs zwei parallele Claude-Code-Sessions im selben Arbeitsbaum, diesmal ohne dass ein Mensch beteiligt war. Session A hatte fünf Umbenennungen von Code-Dateien mit git mv in den Index gelegt, sie aber noch nicht committet. Session B legte in diesem Moment ihren eigenen Commit an, und zwar genau nach der Lehre aus Fall 1, mit git add auf eine einzelne, konkret benannte Datei. Der Commit nahm die fünf fremden Umbenennungen trotzdem mit. Seine Message handelt ausschließlich von der Material-Sammlung, an der Session B gearbeitet hatte.

Der Grund dafür liegt nicht in einem Flüchtigkeitsfehler, sondern in der Art, wie Git arbeitet. git add <pfad> steuert nur, was zusätzlich in den Index wandert. git commit ohne Pfadangabe committet anschließend den kompletten Index, also auch alles, was jemand anderes dort bereits abgelegt hat. In einem Arbeitsbaum, den mehrere Akteure gleichzeitig benutzen, ist der Index damit geteilter Zustand. Wer aus Fall 1 die Lehre „Dateien einzeln nennen“ gezogen hat, ist gegen diesen Fall nicht geschützt und fühlt sich trotzdem sicher.

Session B hatte sogar eine Kontrolle eingebaut, nämlich ein git status im selben Befehl. Es hing aber per && hinter dem Commit:

  1: git add material.md && git commit -m "…" && git status --short

Die Ausgabe erschien also erst, als die Tatsache bereits geschaffen war. Eine Prüfung, die nach der Aktion läuft, ist keine Prüfung mehr, sondern ein Protokoll. Die belastbare Reihenfolge trennt den Blick auf den Index vom Commit und benennt beim Commit die Pfade ausdrücklich:

  1: git diff --cached --name-status     # eigener Schritt vor dem Commit: Was liegt im Index?
  2: git commit --only material.md -m "…"
  3: git show --stat --oneline HEAD      # danach: Enthält der Commit genau die erwarteten Dateien?

Entscheidend ist dabei nicht das Schlüsselwort --only, sondern die Pfadangabe beim Commit. Sobald Pfade auf der Kommandozeile stehen, committet Git laut Dokumentation nur deren aktuellen Inhalt und lässt bereits gestagte Änderungen an anderen Pfaden unangetastet liegen. --only ist dafür der Standard-Modus und macht die Absicht nur ausdrücklich sichtbar. In der Regel-Datei dieses Blogs steht es trotzdem ausgeschrieben, weil man dem Kommando die Absicht dann ansieht. Das Verhalten wurde für diesen Artikel in einem Wegwerf-Repository nachgestellt. Liegt eine fremde Umbenennung im Index, dann ergibt git add b && git commit einen Commit mit zwei Dateien, git commit --only b dagegen einen Commit mit einer einzigen Datei, und die fremde Umbenennung bleibt danach unverändert im Index liegen.

Eine Grenze hat die Pfadangabe allerdings, und sie ist für dieses Thema wichtig. Sie schützt vor fremden Einträgen an anderen Pfaden, nicht vor fremden Änderungen an derselben Datei. Git committet den aktuellen Inhalt der genannten Pfade aus dem Arbeitsbaum, nicht den Stand, den die Session mit git add abgelegt hat. Hat eine andere Session dieselbe Datei kurz vorher ebenfalls bearbeitet, landet ihre Änderung mit im Commit, und kein Kommando warnt davor. Auch das wurde nachgestellt: Der Index trug eine eigene Zeile, der Arbeitsbaum zusätzlich eine fremde, und git commit --only committete beide. git show --stat zeigte danach zwei eingefügte Zeilen statt einer, ein Hinweis, den nur bemerkt, wer die Zahl erwartet. git commit --only löst also das Problem des geteilten Index, nicht das Problem der geteilten Datei.

Auch in diesem Fall war der Schaden die Auffindbarkeit und kein Verlust. An den Umbenennungen selbst änderte sich keine Zeile, aber die Commit-Message beschreibt sie nicht. Was in diesem Fall gut lief, ist zugleich der Kontrast zum nächsten. Session B hat den Fehler selbst bemerkt, ihn nicht eigenmächtig repariert und die andere Session informiert. Eine Reparatur hätte die History weiter beschädigt, denn auf dem betroffenen Commit saß bereits der Folge-Commit von Session A. Ein git reset --soft auf den Stand davor hätte diesen Commit aus dem Branch herausgenommen. Seine Änderungen wären zwar im Arbeitsbaum geblieben, aber der nächste Commit hätte sie unter der Message von Session B aufgesaugt, und der ursprüngliche Commit wäre nicht mehr über den Branch erreichbar gewesen, sondern typischerweise nur noch über das Reflog. Auch das wurde für diesen Artikel im Wegwerf-Repository nachgestellt. Die Entscheidung, die History stehen zu lassen, traf der Maintainer. Session A hat den Bericht nicht einfach geglaubt, sondern mit git show --stat gegengeprüft und danach im Bearbeitungs-Log des betroffenen Artikels einen Verweis auf die tatsächliche Fundstelle hinterlassen. Dieser Verweis schließt die Lücke, die die Commit-Message hinterlassen hat.

Fall 3: Fremde Arbeit gelöscht

Fall 3 ist das Spiegelbild von Fall 1. Dort hat eine Session fremde Arbeit mitgenommen, hier hat sie fremde Arbeit gelöscht. Die Ursache ist dieselbe, der Schaden ist der teurere von beiden.

Im DI²-Projekt arbeiteten zwei Sessions gleichzeitig an verschiedenen Themen. Session A fixte einen Bug, Session B entwarf parallel ein UI-Konzept für ein anderes Feature. Session A hatte beim Start einen Arbeitsbaum ohne offene Änderungen gesehen. Während eines längeren Werkzeug-Laufs tauchten dort zwei fremde Dateien auf, eine geänderte Konzept-Datei und ein neues Mockup. Session A ordnete beide dem eigenen Lauf zu und begründete das damit, ihr Werkzeug habe über den Auftrag hinaus gearbeitet. Dann verwarf sie beide Dateien. Die geänderte Datei holte sie auf die Vorversion zurück, die neue Datei löschte sie.

In der Zusammenfassung an den Maintainer wurde der Fehler noch verschärft. Session A meldete nicht nur den Rückbau, sondern behauptete auch, der verworfene Text habe eine erfundene Nutzer-Zustimmung enthalten. Diese Zustimmung hatte es aber gegeben, nur eben in der anderen Session. Aus einem Löschvorgang wurde so zusätzlich eine falsche Anschuldigung gegen die eigene Werkzeugkette.

Gefunden hat den Fall nicht Session A. Session B stellte ihre Arbeit wieder her und schickte eine Nachricht in die andere Session. Sie ist hier sinngemäß wiedergegeben und nicht wörtlich:

Zwei Dateien aus meinem Arbeitsstand sind gerade verschwunden: die
geänderte Konzept-Datei und das neue Mockup. Beide stammen von mir,
nicht von deinem Werkzeug. Ich habe sie wiederhergestellt.
Bitte künftig keine Wiederherstellungs- oder Aufräum-Operationen
(checkout, restore, Löschen) auf Pfaden, die du in dieser Session
nicht selbst angefasst hast. Wenn dir Dateien auffallen, die du
nicht zuordnen kannst: melden, nicht entfernen.

Die Nachricht hat die Löschung nicht rückgängig gemacht, das hatte Session B schon selbst erledigt. Sie hat aber die Regel formuliert, die später in die Regel-Datei gewandert ist, und sie hat der verursachenden Session eine Information gegeben, die diese aus dem Arbeitsbaum nie hätte lesen können: dass sie nicht allein war.

Dass die Fehlzuordnung nachvollziehbar ist, wurde oben schon beschrieben. Warum sie trotzdem gefährlich ist, zeigt der Unterschied zwischen einem Commit und einer Änderung, die Git noch gar nicht kennt. Ein Commit lässt sich in der Regel rekonstruieren oder mit einem git revert zurücknehmen. Eine verworfene Änderung, die weder committet noch mit git add in den Index gelegt war, kann Git dagegen nicht zurückholen. Hier kam sie nur zurück, weil die zweite Session ihren Stand noch im Speicher hatte. Daraus folgen zwei Lehren. Zerstörende Operationen wie Zurückholen, Zurücksetzen und Löschen wirken auf alles, was unter dem angegebenen Pfad liegt, und nicht nur auf das eigene Werk. Deshalb sind sie im Parallelbetrieb nie bloßes Aufräumen. Und die eigene Zuordnung ist eine Vermutung, keine Beobachtung. „Das war ich“ lässt sich prüfen, über Zeitstempel, Inhalt und Änderungshistorie. Wer es nicht prüft, sollte es auch nicht behaupten, und erst recht nicht in einer Zusammenfassung an den Menschen.

Die Selbstkontrolle hat in diesem Fall nicht funktioniert. Ohne die zweite Session wäre die Arbeit still verschwunden, und die Falschbehauptung wäre als Befund stehen geblieben. Das macht ihn zum unbequemsten und deshalb wertvollsten der fünf Fälle.

Fall 4: Doppelte Nummern, weil Höflichkeit symmetrisch ist

Der vierte Fall hat seine Pointe nicht im Konflikt selbst, sondern in der kooperativen Reaktion darauf. Beide Seiten haben sich anständig verhalten, und genau deshalb ist der Konflikt ein zweites Mal entstanden.

Zwei Sessions im DI²-Projekt legten unabhängig voneinander je einen neuen Bug an. Die Nummern-Vergabe folgt einer dokumentierten Regel: Die höchste vergebene Nummer aus der Index-Datei plus eins ergibt die nächste. Beide Sessions lasen dieselbe höchste Nummer, und beide vergaben dieselbe neue. Beide bemerkten die Kollision, und beide wichen auf die nächste Nummer aus. Die zweite Kollision war identisch mit der ersten.

Eine Prüfung vorab hätte nicht geholfen, denn beide Seiten hatten korrekt gerechnet. Das kritische Zeitfenster liegt zwischen dem Lesen und dem Schreiben. Ein Prüflauf davor hätte beiden Seiten grünes Licht gemeldet und sie in falscher Sicherheit gewiegt. Der erste Reflex, „prüf halt vorher“, behandelt das Problem wie einen Flüchtigkeitsfehler. Tatsächlich ist es ein Wettlauf: Wer zwischen seinem Lesen und seinem Schreiben von der anderen Seite überholt wird, hat trotz korrekter Prüfung verloren. Solche Kollisionen bei fortlaufenden Nummern lassen sich im Parallelbetrieb deshalb nicht ausschließen, sondern nur erkennen und auflösen.

Geholfen haben am Ende zwei Dinge. Das erste war eine Erkennung nach dem Schreiben, und zwar in beide Richtungen. Im Projekt gibt es dafür eine automatische Prüfung, dort Guard genannt, die die Index-Datei und den Dateibestand gegeneinander abgleicht. Sie meldet nicht nur „Nummer doppelt vergeben“, sondern auch „Datei ohne Index-Zeile“ und „Index-Zeile ohne Datei“, also drei verschiedene Zwischenzustände desselben Vorgangs. Eine Prüfung, die nur Dubletten kennt, hätte die späteren Stufen durchgelassen. Bemerkenswert ist die Herkunft dieses Guards. Er war zwei Tage vorher für ein ganz anderes Symptom gebaut worden, nämlich für Zähler im Kopf einer Index-Datei, die vom Inhalt darunter weggedriftet waren. Gefangen hat er dann eine Doppelvergabe durch parallele Sessions, an die beim Bau niemand gedacht hatte. Der Grund dafür ist übertragbar. Der Guard prüft nicht das Symptom, sondern die Bedingung dahinter, die immer gelten muss: Index und Dateibestand müssen sich in beide Richtungen decken. Eine Prüfung, die nur nachgesehen hätte, ob der Zähler stimmt, wäre bei der Kollision stumm geblieben.

Das zweite war eine asymmetrische Auflösungs-Regel. Die andere Session schlug vor, alphabetisch nach Bezeichner zu sortieren, und der kleinere Bezeichner bekommt die kleinere Nummer. Entscheidend ist dabei nicht, dass die Regel besonders klug ist, sondern dass beide Seiten sie ohne Absprache zum selben Ergebnis ausrechnen können. Beim ersten Zusammenstoß ist Ausweichen das anständige Verhalten. Weichen aber beide aus, entsteht derselbe Konflikt erneut, so wie bei zwei Menschen, die sich im Flur gegenseitig den Weg freigeben. Zwei kooperative Parteien kommen ohne eine asymmetrische Regel nicht auseinander. Wer parallele Agenten fährt, braucht deshalb nicht nur eine Erkennung, sondern eine Tie-Break-Regel, die vorher feststeht, also eine Vorfahrtsregel für den Gleichstand. Sonst verhandeln die Agenten, und Verhandeln kostet Runden.

Zwei Nebenbefunde aus der Auflösung gehören noch dazu. Der erste betrifft die Gegenmaßnahme aus Fall 2. git commit --only hält, was es verspricht, verweigert aber bei einer neuen Datei die Arbeit, solange Git die Datei noch nicht kennt. Die Fehlermeldung lautet dann pathspec did not match any file(s) known to git. Man muss die Datei also erst mit git add hinzufügen, und genau dieser Schritt legt den eigenen Eintrag neben die fremden Einträge, die bereits im Index liegen. Der Riegel hat gehalten, denn der Commit nahm nur die eine Datei mit. Der Ablauf zwingt einen aber durch genau den Zustand, den man vermeiden wollte. Der zweite Nebenbefund ist ein Besitz-Kriterium für geteilte Dateien. Eine Datei, die sich nicht aufteilen lässt und Einträge beider Seiten trägt, gehört in den Commit derjenigen Seite, deren inhaltliche Änderung sie trägt, und nicht in den Commit der Seite, die zufällig zuerst fertig ist. Eine der beiden Sessions hatte die Richtung zunächst falsch vorgeschlagen und wurde von der anderen korrigiert. Ihr Commit hätte sonst einen halben Vorgang ohne die zugehörige Dateiverschiebung enthalten.

Fall 5: Geteilt ist auch die Maschine

Die ersten vier Fälle handeln von Kollateralschaden im Repository. Der fünfte trifft die Maschine des Menschen. Parallele Sessions teilen nicht nur das Repository, sondern auch den Rechner, an dem der Mensch gleichzeitig arbeitet. Geteilt ist hier also nicht mehr der Git-Zustand, sondern die Rechenleistung, und dafür gelten andere Werkzeuge, nicht git, sondern tasksettimeout und trap. Die Auslastung der Maschine ist die eine Ressource, deren Verbrauch der Agent verursacht und selbst nicht wahrnehmen kann.

Der Auftrag im DI²-Projekt war ein Test, der nur sporadisch fehlschlug. Die Diagnose ergab keinen langsamen Test, sondern zwei Zeitbudgets, die nicht zueinander passten. Die Test-Bibliothek gibt jedem einzelnen Wartevorgang 1000 ms, der Test-Runner gibt dem ganzen Testkörper 5000 ms, und die dichtesten Tests reihen sieben Wartevorgänge hintereinander. Ohne Last läuft die Datei in gut einer Sekunde durch. Der Defekt ist also überhaupt nur unter Last zu beobachten. Das macht die Verifikation aufwendig für die Maschine, und es macht sie zugleich legitim.

Der Agent erzeugte die Last selbst, mit einer einzigen Zeile. Sie startete 220 CPU-Endlosschleifen im Hintergrund, danach liefen drei bis fünf vollständige Durchgänge der Testsuite, und das Ganze wurde dreimal wiederholt, über insgesamt zwölf Minuten. Auf der 28-Kern-Workstation des Maintainers ergab das ein Lastmittel (Linux load average) von 234, also rund das Achtfache der Kernzahl, und alle Kerne standen auf hundert Prozent. Aufgefallen ist das nicht dem Agenten. Der Maintainer merkte, dass seine Maschine nicht mehr reagierte, öffnete den Prozessmonitor und fragte in einer dritten Session nach, mit Screenshot: „Machst du noch was? Wenn du läufst, steigt die CPU-Auslastung erheblich.“

Der eigentliche Befund ist keine falsche Idee, sondern eine verlorene. Ein früherer Subagent derselben Bug-Strecke hatte dieselbe Frage mit sechs Endlosschleifen beantwortet, die er per taskset auf zwei Kerne gepinnt hatte. Das erzeugt für die Testsuite dieselbe Konkurrenzsituation, der Rest der Maschine bleibt aber frei. Der spätere Subagent ging ungepinnt auf 56 Schleifen, dann auf 200 und schließlich auf 220. Die sparsame Methode war in derselben Strecke also bereits erprobt und wurde beim Hochskalieren nicht wieder aufgegriffen. Wissen, das eine Session hat, ist für die nächste nicht automatisch vorhanden.

Aufgeräumt wurde zwar, aber an der falschen Stelle. Jedes Kommando endete auf kill $(jobs -p), und das hat funktioniert: Es blieben keine Prozesse zurück, und das Lastmittel sank wieder in den einstelligen Bereich. Das Cleanup hing jedoch an der letzten Zeile des Kommandos statt an einem trap. Ein Timeout, ein Absturz oder ein Abbruch durch den Menschen hätte 220 Endlosschleifen bis zum Neustart hinterlassen, und zwar ausgerechnet in dem Moment, in dem der Mensch eingreift, weil die Maschine steht. Auch an Transparenz fehlte es nicht, nur kam sie im falschen Tempus. Der Abschlussbericht nannte die Methode korrekt und unaufgefordert, mit Lastmittel und Kernzahl. Diese Offenlegung war vollständig und zugleich wirkungslos, weil sie erst kam, als der Schaden vorbei war. Eine Ankündigung vorher hätte zwei Sätze gekostet.

Warum bemerkt der Agent das nicht? Messen könnte er die Last durchaus, mit uptime oder top stünde das Lastmittel in seiner Ausgabe. Was ihm fehlt, ist die Rückmeldung, was diese Zahl für den Menschen bedeutet. Er sieht Exit-Codes und keine ruckelnde Oberfläche, und kein Werkzeug sagt ihm, welche Auslastung der Besitzer der Maschine gerade hinnehmen will. Die Kosten fallen vollständig bei einem Menschen an, den er nicht beobachten kann. Anders als bei Token oder Laufzeit fehlt hier jede Rückmeldeschleife. Ressourcen-intensive Verifikation bleibt trotzdem richtig, denn Last und Wiederholung sind oft der einzige Weg, ein sporadisches Problem überhaupt zu belegen. Drei Auflagen machen sie verträglich. Die Last läuft gepinnt statt auf der ganzen Maschine, sofern der Test keine systemweite Konkurrenz voraussetzt. Das Cleanup hängt an einem trap statt an der letzten Zeile. Und der Agent sagt vorher an, was er vorhat, statt hinterher zu berichten, denn ob eine Maschine zwölf Minuten unbenutzbar sein darf, entscheidet ihr Besitzer. Das folgende Minimalmuster zeigt die ersten beiden Auflagen im Code. Ein Runbook ist es nicht, und eine Aufräum-Garantie auch nicht:

  1: trap 'kill $(jobs -p) 2>/dev/null' EXIT INT TERM
  2: for i in $(seq 1 6); do taskset -c 0,1 timeout 900 sh -c 'while :; do :; done' & done
  3: taskset -c 0,1 <testlauf>

Zeile 2 erzeugt die Konkurrenz auf genau den zwei Kernen, auf denen in Zeile 3 auch der Test läuft, und gibt jeder Schleife ein eigenes timeout, nach dem sie sich selbst beendet. Der Rest der Maschine bleibt dem Menschen. Zeile 1 räumt die gestarteten Schleifen auf, wenn die Shell endet. Zwei Grenzen hat dieser trap, und beide wurden für diesen Artikel nachgestellt. Kommt ein Abbruch-Signal, während der Testlauf noch läuft, führt Bash den trap erst aus, wenn der Testlauf zurückkehrt. Und gegen ein kill -9 der Shell selbst hilft er gar nicht. Genau dafür ist das timeout in Zeile 2 da. Die dritte Auflage gehört in den Auftrag, und dort kostet sie zwei Sätze. Der folgende Auftrag ist ein Muster und kein Zitat aus der Session:

Reproduziere den sporadischen Fehlschlag unter Last. Auflagen:
Last nur auf zwei gepinnte Kerne (taskset), Cleanup per trap,
und sag mir vorher, wie lange die Maschine belastet sein wird.
Wenn du mehr als zwei Kerne brauchst, frag zuerst.

Ein Nebenbefund betrifft die Zuordnung. Drei Sessions liefen parallel, und der Maintainer konnte nicht erkennen, welche davon die Last verursachte. Der Beleg kam am Ende aus den Sitzungs-Transkripten, in denen das Kommando mit Zeitstempel stand, und aus dem Abklingen des Lastmittels, das zeitlich zum letzten protokollierten Lauf passte. Wer mehrere Agenten parallel fährt, braucht eine Methode, die Last einer Session zuzuordnen, und zwar bevor er sie braucht.

Was hält und was nicht

Aus den fünf Fällen sind Gegenmaßnahmen entstanden, und zwar nachträglich. Nach Fall 2 wanderten sie in die Regel-Datei für Git im Repository dieses Blogs, als eigene Sektion „Parallele Sessions“, und seit Fall 4 tragen sie auch den Nebenbefund zu neuen Dateien. Wie so eine Regel-Datei aufgebaut ist und warum die Gegenmaßnahme dort lebt und nicht im Kopf des Maintainers, beschreibt 27 Regel-Dateien statt einer CLAUDE.md. Die Bilanz nach gut einer Woche mit parallelen Claude-Code-Sessions sieht so aus.

Was hält:

  • git commit --only <pfade>: Die Pfadangabe beim Commit begrenzt ihn auf die angegebenen Pfade, das können auch Verzeichnisse oder Muster sein, und lässt gestagte Änderungen außerhalb davon liegen, --only macht das ausdrücklich. Gegen fremde Index-Einträge ist das die einfachste und robusteste Variante, weil sie nicht von der Aufmerksamkeit der Session abhängt. Gegen fremde Änderungen an derselben Datei schützt sie nicht.
  • Der Blick auf den Index als eigener Schritt vor dem Commit: Ein git diff --cached --name-status oder ein git status --short läuft als eigenes Kommando vor dem Commit und nicht per && dahinter. Was dort liegt und nicht zur eigenen Änderung gehört, wird benannt oder ausgeschlossen. Diese Prüfung ist eine Plausibilitätskontrolle und keine Sperre, denn zwischen Blick und Commit kann eine andere Session den Index erneut verändern.
  • git show --stat --oneline HEAD nach dem Commit: Dieser Blick kontrolliert, ob der Commit die erwarteten Dateien enthält und nur diese. Er findet den Fehler, den die beiden Schritte davor durchgelassen haben. Ob auch der Inhalt der Dateien stimmt, zeigt erst git show HEAD -- <pfad>.
  • Melden statt reparieren: Sitzt fremde Arbeit im eigenen Commit auf einem Branch, an dem noch jemand anderes arbeitet, informiert die Session die betroffene Seite und überlässt die Entscheidung über die History dem Maintainer. Bleibt die History stehen, hinterlässt sie dort einen Verweis auf die Fundstelle, wo jemand danach suchen würde.
  • Ein eigener Worktree pro Session, wo der Ablauf es zulässt: Mit git worktree add bekommt jede Session einen eigenen Arbeitsbaum samt eigenem Index, und die Fälle 1 bis 3 entfallen in der hier beschriebenen Form. Geteilt bleiben die Repository-Objekte und die Branches, eigen sind je Worktree unter anderem HEAD und der Index. Geteilt bleibt außerdem alles außerhalb des Arbeitsbaums, also auch die Maschine und die geteilten Zähler, mehr dazu in der FAQ.
  • Tie-Break-Regel vorher festlegen: Für jeden geteilten Zähler gilt eine asymmetrische Regel, die beide Seiten ohne Absprache zum selben Ergebnis rechnen können.
  • Die Bedingung prüfen, nicht das Symptom: Der Guard gleicht Index und Dateibestand in beide Richtungen ab. Eine solche Prüfung fängt auch Fehler, an die beim Bau niemand gedacht hat.
  • Gegenprüfen statt glauben: Beide Sessions in Fall 4 haben die Zustandsmeldungen der jeweils anderen nachgemessen, statt sie zu übernehmen. Das hat eine Auslassung im Bericht gefunden, die dem Berichtenden selbst nicht auffallen konnte.

Was nicht hält:

  • „Prüf halt vorher“: Bei einem Wettlauf zwischen zwei Schreibern liegt das Zeitfenster zwischen Lesen und Schreiben. Die Prüfung vorab meldet grünes Licht und wiegt in Sicherheit.
  • Die Lehre aus Fall 1 als Schutz gegen Fall 2: git add <datei> statt git add -A schließt eine Lücke und lässt die benachbarte offen. Eine Lehre, die eine Lücke schließt, erzeugt ein falsches Sicherheitsgefühl für die nächste.
  • Reparatur per git reset <commit>: Das Zurücksetzen des Branches wirkt auf alles, was seit diesem Commit dazugekommen ist. Sitzt bereits ein Commit einer anderen Session darauf, verschwindet er aus dem Branch. Die drei Modi unterscheiden sich nur darin, was außer dem Branch noch zurückgesetzt wird: --soft lässt Index und Arbeitsbaum stehen, der Standard-Modus setzt den Index zurück, und --hard verwirft zusätzlich ungesicherte Änderungen an getrackten Dateien im Arbeitsbaum, auch die einer anderen Session, und überschreibt untracked Dateien, die einer getrackten Datei des Zielstands im Weg stehen. Der Schaden der Reparatur ist dann größer als der des Fehlers.
  • Cleanup in der letzten Zeile: Es überlebt genau den Abbruch nicht, der es am dringendsten braucht.
  • Transparenz im Nachhinein: Ein vollständiger Bericht nach dem Schaden ist ehrlich, aber er ändert nichts mehr.

Ein Nebenbefund gehört an diese Stelle, weil er die Kennzahlen betrifft, mit denen Agenten ihre Sorgfalt belegen. Ein geteilter Arbeitsbaum verfälscht nicht nur Dateien, sondern auch Messungen. Ein vollständiger Testlauf, gestartet in dem Moment, in dem die andere Session gerade eine Datei umbenannte, meldete drei fehlgeschlagene Testdateien mit einem Fehler, der inhaltlich nichts mit ihnen zu tun hatte. Beim nächsten Lauf war alles grün. Wer solche Zahlen in einen Bericht schreibt, dokumentiert einen Zustand, den es nie gegeben hat. Im Parallelbetrieb ist ein einzelner roter Lauf deshalb kein Befund, sondern eine Hypothese, und er gilt erst, wenn er sich auf einem ruhigen Baum wiederholen lässt. Wie sich Messungen von Vermutungen trennen lassen, wenn der Agent die Diagnose übernimmt, wird ein eigener Artikel dieses Blogs zur Fehlersuche mit einem Coding-Agenten behandeln.

Zusammenfassung

  • Eigene und fremde Änderungen im Arbeitsbaum sehen für eine Session gleich aus, und Git widerlegt die Annahme „alles Neue stammt von mir“ nirgends.
  • Im gemeinsam benutzten Arbeitsbaum ist der Git-Index geteilter Zustand. git add <datei> schützt nicht, weil git commit ohne Pfadangabe den kompletten Index committet. Gegen fremde Einträge an anderen Pfaden ist git commit --only <pfade> die robusteste der hier betrachteten Varianten, ergänzt um einen Blick auf den Index vor dem Commit und ein git show --stat danach. Gegen gleichzeitige Änderungen an derselben Datei hilft am zuverlässigsten ein eigener Worktree.
  • Zerstörende Operationen sind im Parallelbetrieb nie bloßes Aufräumen. Die eigene Zuordnung ist eine Vermutung, und wer sie nicht prüfen kann, meldet, statt zu löschen.
  • Geteilte Zähler können im Parallelbetrieb jederzeit kollidieren. Nötig sind eine Prüfung nach dem Schreiben, die den Soll-Zustand kontrolliert, und eine asymmetrische Tie-Break-Regel, die vorher feststeht.
  • Parallele Sessions teilen auch die Maschine des Menschen, und deren Auslastung ist eine Ressource ohne Rückmeldeschleife zum Agenten. Ressourcen-intensive Verifikation läuft deshalb gepinnt, zeitlich begrenzt, mit Cleanup per trap und mit einer Ansage vorher.
  • Die Regel kam nach dem Schaden. Drei der fünf Fälle waren Fehler des Agenten. Zwei davon hat der Agent selbst gemeldet, den dritten hat die geschädigte Nachbar-Session gefunden.

FAQ

Reicht es, Dateien einzeln zu stagen statt git add -A zu verwenden?

Nein. git add <datei> steuert nur, was zusätzlich in den Index wandert. Liegt dort bereits etwas von einer anderen Session oder vom Menschen, nimmt git commit ohne Pfadangabe es mit. Schutz bietet erst git commit --only <pfade>, das den übrigen Index ignoriert, oder ein Blick auf den Index in einem eigenen Schritt vor dem Commit. Ein git status, das per && hinter dem Commit hängt, zeigt den Fehler erst, wenn er bereits passiert ist.

Kann git commit --only fremde Änderungen verhindern?

Nur die eine Art davon. Die Pfadangabe hält gestagte Änderungen an anderen Pfaden aus dem Commit heraus, das ist das Problem des geteilten Index aus Fall 2. Sie hält aber nicht heraus, was eine andere Session an derselben Datei geändert hat, denn Git committet den aktuellen Inhalt der genannten Pfade aus dem Arbeitsbaum. Wer sicher sein will, was im Commit steht, prüft den Inhalt mit git show HEAD -- <pfad> nach oder gibt jeder Session von vornherein einen eigenen Worktree.

Was passiert, wenn zwei Sessions dieselbe Datei bearbeiten?

Im geteilten Arbeitsbaum gibt es die Datei nur einmal, und Git meldet nichts. Ändern beide gezielt einzelne Stellen, stehen danach beide Änderungen in der Datei, und wer zuerst committet, nimmt die fremde mit. Schreibt eine Session die Datei dagegen komplett aus einem älteren Lesestand neu, ist die Änderung der anderen still verschwunden. War sie gestaged, liegt ihr Stand noch als Blob im Index. Nach einem erneuten git add ist er nur noch ein verwaistes Objekt, das git fsck --lost-found unter Umständen findet, eine Garantie ist das nicht. War sie nie gestaged, ist sie weg. Erkennen lässt sich das nur an Indizien: ein MM in git status an einer Datei, die man selbst nicht gestaged hat, oder ein Editier-Werkzeug, das das Schreiben verweigert, weil sich die Datei seit dem letzten Lesen geändert hat. Das Edit-Werkzeug von Claude Code hat sich in den Sessions hinter diesem Artikel genau so verhalten, Stand September 2026, ein sed oder ein Python-Skript tut das nicht. Zuverlässig erkennt Git die Situation erst, wenn beide Sessions in eigenen Worktrees auf eigenen Branches arbeiten. Dann gibt es zwei Versionen, und beim Zusammenführen entsteht ein Merge-Konflikt, sobald beide dieselben Zeilen geändert haben. Verschiedene Zeilen führt Git automatisch zusammen.

Was tut man, wenn ein Commit fremde Arbeit mitgenommen hat?

Auf einem Branch, an dem noch jemand anderes arbeitet, ist der erste Reflex, den Commit eigenmächtig zu reparieren, der falsche. Wer allein auf einem unveröffentlichten Branch arbeitet, darf ihn zurücksetzen. Ein git reset auf einen früheren Commit und andere Eingriffe in die History wirken auf alles, was seit diesem Commit dazugekommen ist. Sitzt bereits ein Commit einer anderen Session darauf, nimmt die Korrektur ihn aus dem Branch, und mit --hard verwirft sie auch deren ungesicherte Änderungen an getrackten Dateien. Besser ist es, die betroffene Seite zu informieren und den Maintainer entscheiden zu lassen, ob die History angefasst wird. Bleibt sie stehen, gehört ein Verweis auf die tatsächliche Fundstelle dorthin, wo jemand danach suchen würde, etwa in das Bearbeitungs-Log des betroffenen Artikels oder in die Spec der betroffenen Aufgabe. Der Inhalt ist nicht verloren. Nur seine Herkunft ist über die Commit-Message nicht mehr auffindbar.

Wie erkennt eine Session, dass sie nicht allein im Repository ist?

Mit Git-Mitteln allein zuverlässig gar nicht, und genau das ist der Kern des Problems. Es gibt Indizien: ein Index, der beim Start nicht leer ist, Dateien, die während eines Werkzeug-Laufs neu auftauchen oder sich ändern, obwohl das eigene Werkzeug sie nicht angefasst hat, oder ein Commit im Log, der nicht von der eigenen Session stammt. Keines davon ist ein Beweis. Deshalb behandelt eine Session ihre Zuordnung als Vermutung und prüft sie über Zeitstempel und Inhalt, bevor sie etwas verwirft. Am verlässlichsten ist die Information vom Menschen selbst. Wer zwei Sessions startet, sagt beiden, dass sie nicht allein sind.

Sind Git-Worktrees die Lösung für parallele Claude-Code-Sessions?

Für die Fälle 1 bis 3 lösen sie das Problem weitgehend, weil sie dessen gemeinsame Ursache beseitigen, den geteilten Arbeitsbaum. Ein Worktree gibt jeder Session einen eigenen Arbeitsbaum mit eigenem Index, geteilt bleiben nur das Repository, seine Objekte und seine Branches. Ein Worktree bedeutet in der Praxis auch einen eigenen Branch pro Session oder einen losgelösten HEAD, denn ein Branch lässt sich nicht in zwei Worktrees gleichzeitig auschecken. Der Merge zurück gehört also zum Ablauf, und dort meldet Git dann auch die Konflikte an derselben Datei. Der Unterschied zum gemeinsamen Arbeitsbaum liegt genau darin: eigene Dateien auf der Platte, eigener Index, eigener HEAD, aber dieselben Commits und Branches darunter. Fremde Dateien im Arbeitsbaum und fremde Einträge im Index entfallen damit. Die übrigen Fälle bleiben aber bestehen. Die Maschine ist weiterhin geteilt, und geteilte Zähler kollidieren dann eben beim Zusammenführen der Branches statt beim Schreiben. Außerdem arbeitet der Mensch in der Praxis oft im Haupt-Arbeitsbaum, und eine Session, die dort läuft, hat wieder alle fünf Probleme. Worktrees trennen die Arbeitsbäume, nicht die Akteure.

Wie beauftragt man Last-Tests, ohne die eigene Maschine lahmzulegen?

Am besten mit drei Auflagen im Auftrag. Die Last läuft gepinnt auf wenige Kerne, etwa per taskset, sofern der Test keine systemweite Konkurrenz voraussetzt, und der Test läuft auf denselben Kernen, damit die Konkurrenz dort entsteht, wo sie gebraucht wird. Das Cleanup hängt an einem trap, damit es beim Ende des Skripts greift, und jede Last-Schleife bekommt ein eigenes timeout, damit sie sich auch dann beendet, wenn die Shell hart abgeschossen wird. Und der Agent sagt vorher an, wie lange die Maschine belastet sein wird, damit der Mensch entscheiden kann, ob er das gerade zulassen will. Ein Agent kann diese Auflagen nicht von sich aus herleiten, weil er die Last zwar messen, ihre Kosten für den Menschen aber nicht sehen kann.

Verwandte Artikel

Übergeordnet:

Hub:

Regel-Dateien:

Nebenan: