Zwei Sessions, ein Arbeitsverzeichnis
Eine zweite Session verwarf den nicht committeten Arbeitsbaum einer ersten. Warum davon nichts zurückkam, und drei Vorkehrungen.
Veröffentlicht am Kategorie: Aus der Praxis
Auf dieser Seite
Zwei Claude-Sessions liefen im selben Repo, im selben Arbeitsverzeichnis. Die zweite hat git reset
ausgeführt und die rund 103 geänderten, noch nicht committeten Dateien der ersten verworfen.
Zurückgeholt wurde davon nichts. Die Agenten schreiben direkt auf die Platte, ohne git add, und was
nie in der Objektdatenbank lag, findet auch kein Reflog. Drei Vorkehrungen hätten den Schaden auf
eine Welle statt vier begrenzt, zwei davon sind je ein Kommando.
Was passiert ist
Am 28. Mai 2026 lief in einem Repo eine Session über vier Wellen. Eine Migration der Fehlerbehandlung, 582 umgestellte throw-Stellen, rund 480 vorher übersprungene Tests wieder aktiviert, Typecheck auf null Fehler. Nichts davon war committet, und das war so vorgesehen: die Agenten bearbeiten Dateien direkt und unstaged, der Commit kommt am Ende.
Parallel lief eine zweite Session. Kein zweiter Klon, kein zweiter Arbeitsbaum, dasselbe
Verzeichnis, laut Notiz sogar dieselbe semantische Session-ID. Diese zweite Session führte
git reset aus, verwarf den kompletten Arbeitsbaum mit rund 103 geänderten Dateien und begann
danach ihre eigene Arbeit an anderen Routen.
Vier Wellen Arbeit waren damit weg. Wie viel davon nachgebaut wurde und wie lange das gedauert hat, steht nirgends.
Die Lernnotiz vom selben Tag hält es so fest (Konfidenz 0.85, Status verified):
"The work was UNRECOVERABLE: agents edit in-place unstaged (per orchestrator instruction), so reset/restore leaves no stash, no commit, no dangling blobs."
Die Notiz trägt als Quell-Session unknown, die zugehörige Session-Datei fehlt. Warum die zweite
Session das Reset gefahren hat, steht nirgends. Mehr Details habe ich dazu nicht.
Warum davon nichts zurückkam
git hat drei Orte für Inhalt, und nur zwei davon sind Rettungsanker.
Die Objektdatenbank. git add schreibt den Dateiinhalt als Blob nach .git/objects. Ab diesem
Moment existiert er, auch ohne Commit. Ein hartes Reset macht daraus einen dangling blob, und
git fsck --lost-found findet ihn wieder.
Das Reflog. Jeder Commit, jeder Branch-Wechsel, jedes Reset hinterlässt dort einen Eintrag. Das ist der Fall, an den die meisten denken, wenn sie sagen, git verliere nichts.
Das Arbeitsverzeichnis. Eine geänderte Datei, die weder gestaged noch committet ist, ist für git eine Datei auf der Platte. Kein Objekt, kein Hash, kein Eintrag.
Die Agenten schrieben per Anweisung in-place und unstaged. Damit lag die Arbeit aus vier Wellen vollständig im dritten Fall. Der Satz "git verliert nichts" gilt für alles, was git jemals gesehen hat. Über deinen Arbeitsbaum sagt er gar nichts.
Nachprüfen lässt sich das in zwei Zeilen:
git reflog
git fsck --lost-found
Lag deine Arbeit nur im Arbeitsverzeichnis, sehen beide Ausgaben völlig normal aus und keine enthält
sie. Das Reflog zeigt jede Position, die HEAD hatte, nicht das, was daneben auf der Platte lag.
git fsck --lost-found zeigt Objekte, die an keinem Ref mehr hängen, und eine unstaged Datei war nie
eines. Dann bleiben Editor-Backups, die Dateiversionen des Betriebssystems und das
Terminal-Scrollback.
Der zweite Vorfall, bei dem nichts passiert ist
Drei Wochen später, anderes Repo, 21. Juni 2026. Ein Review-Agent, der per Vertrag nur lesen darf,
brauchte einen Diff gegen eine Baseline. Er führte git stash aus, wechselte per git checkout auf
den Basis-Ref, verglich, und stellte danach alles wieder her. Der Baum war hinterher byte-identisch,
HEAD intakt, der Stash leer, 572 Tests grün.
Die Notiz dazu ist trotzdem als anti-pattern abgelegt:
"Working tree verified byte-intact, but this is a PSA-003 violation that risks data loss."
Der Rest der Notiz sagt, was der Agent stattdessen hätte tun sollen: gegen den Basis-Ref diffen oder
sich die Datei per git show ausgeben lassen, niemals stash oder checkout.
Es ist also nichts passiert. Das ist ein Ergebnis, keine Eigenschaft. Es hing daran, dass in diesem Fenster kein anderer Agent dieselben Dateien anfasste.
Wie schmal das Fenster ist, zeigt ein dritter Fall vom 6. August 2026. Ein Agent in einer Welle
wollte belegen, dass eine prettier-Warnung schon vor seiner Änderung bestand, und griff dafür zu
git stash. Damit lagen 58 Einträge im Stash, 24 davon untracked, also die laufende Arbeit aller
parallelen Agenten, an einem Ort, an dem keiner von ihnen gesucht hätte. Bis zum pop vergingen
rund 40 Sekunden. In dieser Zeit schrieben drei andere Agenten weiter, git status zählte danach 63
statt 60 Einträge. Der pop ging konfliktfrei durch, weil die drei zufällig andere Dateien betrafen.
Die Ausgangsfrage brauchte den Arbeitsbaum gar nicht:
git show HEAD:pfad/zur/datei.ts | pnpm exec prettier --stdin-filepath pfad/zur/datei.ts --check
Zweimal ist nichts passiert, und beide Male hing es daran, welche Dateien die anderen Agenten zufällig gerade angefasst haben. Das ist kein Sicherheitsmodell, das ist Glück mit Protokoll.
Wie oft so etwas vorkommt
Ich führe zu jeder Session Lernnotizen in einem eigenen Verzeichnis. Der Messweg:
cd ~/Projects/vault/40-learnings
find . -name '*.md' | wc -l
grep -rilE 'git reset|git stash|uncommitted working tree|unrecoverable' --include='*.md' . | wc -l
41 von 4421 Notizen, Stand heute. 22 davon nennen git stash namentlich. Verteilt über mehr als ein
Dutzend Repos und über Monate. Das sind Nennungen, keine bestätigten Vorfälle. 41 ist die
Obergrenze, nicht die Zahl.
Der unangenehme Teil daran: ein Verbot im Prompt reicht nicht. In einer Session vom 16. Juli 2026 stehen drei Vorfälle, alle nach ausdrücklichem Verbot, alle vom jeweiligen Agenten selbst offengelegt und selbst zurückgenommen. Geändert hat sich danach nicht die Formulierung des Verbots, sondern die Liste der Kommandos, die der Agent gar nicht mehr ausführen kann.
Und eine Zahl aus einer Notiz vom 2. Juni 2026 erklärt, warum das kein Randfall ist: auf dem Host liefen zu dem Zeitpunkt 24 Claude-Prozesse, und das Session-Lock wurde mitten in der Session von einem Peer neu genommen.
Drei Vorkehrungen
Checkpoint-Commit nach jeder Implementierungs-Welle
Die Gegenmaßnahme steht in der Notiz zum ersten Vorfall wörtlich drin: bei geteiltem Arbeitsbaum nach jeder Implementierungs-Welle auf einem Session-Branch committen, statt einmal am Ende. Das verhindert das Reset nicht. Es begrenzt den Schaden auf die laufende Welle.
Zwei Details hängen daran, und beide haben eigene Vorfälle:
git add -A und git commit -a nehmen in einem geteilten Baum die Arbeit der anderen Session mit.
Genau so ist am 2. Juni 2026 der Refactor einer Peer-Session in einen fremden Commit gerutscht,
geheilt per Amend, verloren war nichts. Ein blanker
git commit committet alles Gestagte, auch das, was ein anderer Agent zwischen deinem add und
deinem commit gestaged hat. Die Notiz dazu steht mit Konfidenz 1.0 und zwei belegten Vorfällen in
einer einzigen Session. Also Pathspecs, immer:
git switch -c session/2026-08-14-migration
git commit -m "wip(w2): checkpoint" -- src/lib/a.ts src/lib/b.ts
Neue, noch untracked Dateien kommen so nicht mit. Die stagst du einzeln mit git add pfad/zur/datei,
nie mit -A.
Session-Lock am Start
Auch das steht in der Notiz zum ersten Vorfall, als Nebensatz: den Wert aus dem Session-Lock beim Start festhalten, und wenn er sich mitten in der Session ändert, hat eine andere Session übernommen. Dann nicht weiterarbeiten, sondern zuerst den Baum prüfen.
# einmal am Session-Start, den ausgegebenen Wert merken
uuidgen | tee .git/session.lock
# vor jeder Welle
cat .git/session.lock
Welcher Wert drinsteht, ist egal, er muss nur zwischen zwei Sessions verschieden sein. $$ taugt
dafür nicht: jede Bash-Zeile läuft in einer neuen Shell und hat damit eine neue PID.
Das verhindert keinen Verlust. Es verkürzt die Zeit, bis du ihn bemerkst, und zusammen mit Checkpoint-Commits ist genau das der Unterschied zwischen einer verlorenen Welle und vier.
Read-only heißt diff, nicht stash
Ein Review- oder Test-Agent, der eine Baseline braucht, hat sie bereits. HEAD, jeder Ref und jeder Commit sind lesbar, ohne den Arbeitsbaum anzufassen:
git diff HEAD
git diff <basis-ref>..HEAD
git show HEAD:pfad/zur/datei.ts
git show HEAD:pfad/zur/datei.ts | pnpm exec prettier --stdin-filepath pfad/zur/datei.ts --check
Wenn ein Agent trotzdem stasht, gehört der pop in denselben Arbeitsschritt und nicht in ein Aufräumen danach. In einem Fall vom 8. Mai 2026 hatte ein Testagent eine Katalogdatei gestasht und nicht zurückgeholt. Aufgefallen ist es erst beim Aufräumen durch den Koordinator, nach dem pop standen die 204 Einträge der Datei wieder vollständig drin.
Der Fix, der die Ursache wegnimmt
Die drei Vorkehrungen oben begrenzen den Schaden.
git worktree add nimmt die Ursache weg: jede Session
bekommt ein eigenes Verzeichnis bei gemeinsamer Objektdatenbank, ein Reset in der einen erreicht die
andere nicht mehr.
Gratis ist das nicht. Getrennte Bäume heißen getrennte Installationen und getrennte Build-Caches, und bei kurzen Sessions geht sich das oft nicht aus. Wer parallele Sessions regelmäßig fährt, hat hier trotzdem die einzige Maßnahme auf dieser Seite, die die Form des Problems ändert statt die Größe des Verlusts.
Die Falle im Sicherheitsnetz
Der naheliegende Reflex ist ein automatischer Snapshot vor jedem Dispatch. Ich hatte den, und er hat das Falsche gesichert.
Der Snapshot lief über git stash create, weil das den Arbeitsbaum nicht anfasst. Untracked Dateien
waren darin nicht enthalten. Bei Wellen, die viele neue Dateien erzeugen, waren die Snapshots damit
praktisch leere Backups: das Kommando lief sauber durch, die Sicherung war vorhanden, und im
Ernstfall wäre sie wertlos gewesen. Aufgefallen ist es erst, als jemand die Bäume der gespeicherten
Snapshots direkt aufgelistet hat.
Der offensichtliche Fix, das Flag -u anzuhängen, hat es nicht behoben. Nach der Messung auf git
2.53 nimmt git stash create das Flag entgegen und ignoriert es; nur push und save beachten es
(die Doku führt -u bei push und save, nicht bei create,
abgerufen am 2026-08-14). Diese Notiz steht auf Entwurf mit Konfidenz 0.6, also prüf das auf deiner
Version selbst.
Was daraus bleibt: ein Backup ist erst dann eines, wenn du seinen Inhalt gelesen hast. Exit-Code 0 ist keine Sicherung.
Was die drei Maßnahmen nicht abdecken
Das sind drei Einzelmaßnahmen aus zwei Vorfällen. Sie decken genau die Vorfälle ab, aus denen sie stammen. Was sie nicht sind: ein Verfahren, das aus jedem neuen Vorfall die nächste Regel macht, die Regeln nachweisbar hält und die wieder loswird, die nicht mehr greifen. Genau darum geht es in Modul 3 im Kurs zur Multi-Agent-Orchestrierung, wenn du parallele Wellen nicht einmal überlebst, sondern regelmäßig fährst.
Was ich nicht weiß
Ob die zweite Session das Reset selbst beschlossen hat oder auf Anweisung fuhr, weiß ich nicht, und
ich werde es nicht mehr herausfinden. Also ein Gegenbeispiel, damit hier niemand zu schnell aufgibt:
im Juni 2026 galt ein Commit nach gründlicher Suche als verloren. Nicht auf GitLab, nicht auf
GitHub, in keinem lokalen Klon, nicht im Papierkorb, nicht in der Dateisuche. Ein git pull hat ihn
dann hereingezogen, weil eine andere
Maschine dieselbe Arbeit unter einer anderen SHA gepusht hatte, 28 Dateien, vollständig. "Hier und
jetzt nicht erreichbar" ist nicht dasselbe wie "weg". Bei einem unstaged Arbeitsbaum hilft das
nichts, bei einem verschwundenen Commit sehr wohl.
Häufige Fragen
Reicht ein zweites Terminal, oder brauche ich einen zweiten Arbeitsbaum?
Einen zweiten Arbeitsbaum, siehe oben. Ein zweites Terminal teilt sich dasselbe Verzeichnis, und genau daran ist der erste Vorfall gescheitert.
Ist git stash grundsätzlich schlecht?
Nein. In einem Baum, in dem nur du arbeitest, ist es das richtige Werkzeug. Das Problem ist nicht das Kommando, sondern seine Reichweite: es greift auf den ganzen Baum zu, auch auf die Teile, die einem anderen gehören.
Wie viel Zugriff sollte ein Agent überhaupt bekommen?
Das ist die andere Hälfte derselben Frage, und sie ist in sechs Stufen vor dem Schreibzugriff ausführlicher beantwortet als hier. Kurz gesagt: der Schreibzugriff auf den Arbeitsbaum ist eine Stufe, die Kommandos, die den Baum als Ganzes bewegen, sind eine deutlich höhere. Wo der Agent dabei läuft, ist noch einmal eine eigene Frage, und dazu steht mehr in ein lokales Modell an Claude Code.