Ein Klick auf Regenerate und 107 Nachrichten waren weg


Die letzte Antwort war nicht ganz richtig, also klickst du auf Regenerate. Die UI aktualisiert sich, alles sieht gut aus — bis du nach oben scrollst und die Unterhaltung von vor vier Stunden verschwunden ist, zusammen mit allem zwischen damals und jetzt. Du hast nichts gelöscht. Du hast nie auf etwas geklickt, das „clear“ hieß.

Dieser Fall wurde tatsächlich gemeldet: Ein einziges Regenerate verwandelte eine Sitzung mit 430 Nachrichten in 323. 107 aktive Nachrichten wurden still archiviert, einschließlich aller Nachrichten nach einem Turn von vor vier Stunden.

Aus 430 wurden 323

Der Bug steckt in der Frage „Zu welcher Zeile spult dieses Regenerate zurück?“.

In Desktop erarbeiten regenerate und edit zuerst einen Truncation-Plan: die Zeile, die neu gemacht wird, und alles danach gilt als History. Der Server validierte diesen Plan beim Absenden, lehnte aber nur drei Fälle ab:

  • veraltete Anker (Fehler 4018);
  • Ordinal-Drift (4030);
  • Kürzungen des gesamten Transcripts (4028).

Was durch alle drei hindurchschlüpfte: ein gültiger row-id-Anker, der auf eine sehr alte Zeile zeigte. Und der Regenerate-Pfad des Clients vertraute auf seinen eigenen parentId, ohne nachzufragen. Zusammen konnte ein Regenerate auf der letzten Nachricht den Turn auf Ordinal 31 legitim erneut abspielen und die gut hundert Nachrichten danach archivieren.

Auf main reproduziert: Eine Sitzung mit 430 Nachrichten wurde bei Ordinal 31 auf 310 gekürzt, ohne dass etwas im Weg stand. Klar gesagt: Die clientseitige Ursache ließ sich nicht reproduzieren, und kein einzelner schuldiger Commit wurde identifiziert. Anders ausgedrückt: Mehr als ein Pfad kann diese Klasse von Fehlauslösung auslösen — deshalb sitzt der Fix am Ausgang und nicht an einem einzelnen Einstieg.

Jetzt wird zuerst gefragt

Der Ansatz lautet nicht „geschickter erraten, welchen Turn du gemeint hast“. Er lautet: dir die Konsequenz zeigen, bevor die Aktion läuft.

  • Regenerate (planConfirmedReload) und edit (planConfirmedEdit) teilen sich jetzt eine einzige Deep-Cut-Prüfung (confirmIfDeepCut);
  • Jeder Rewind, der einen späteren sichtbaren User-Turn verwerfen würde, öffnet den bestehenden destruktiven Bestätigungsdialog (deepCutConfirmRequest, mit der bestehenden Restore-Formulierung, sodass keine neuen Interface-Strings hinzugekommen sind);
  • Was ununterbrochen bleibt, ist ebenfalls explizit definiert: ein Tail-Regenerate, ein Edit des letzten Turns und ein Edit eines fehlgeschlagenen Turns fragen nicht nach;
  • Wenn du ablehnst, wird nichts gesendet. Restore behält seinen eigenen separaten Dialog.

Ein Dialog kann lange offen bleiben — du warst Kaffee holen —, also werden Änderungen in diesem Fenster behandelt:

  • Regenerate verwirft den Plan, wenn die Sitzung busy wurde oder sich ihr Transcript geändert hat;
  • Edit zielt mit revalidateEditPlan erneut auf das aktuelle Transcript, sodass eine wachsende Streaming-Antwort berücksichtigt wird; es stoppt mit einem Hinweis, wenn sich der editierte Turn verschoben hat oder ein neuer späterer User-Turn hinzugekommen ist;
  • Beide verwerfen die Aktion, wenn du die Sitzung gewechselt hast.

Der Server zählt mit

Der Client-Prompt ist eine Ebene. Der Server zählt jetzt tatsächlich, was eine Kürzung verwerfen würde: _archived_user_turns misst die Kürzung in dauerhaften User-Zeilen, und jedes Detail neigt zur konservativen Seite:

  • Ein reparierter Carrier zählt jede Zeile seines zusammengeführten user;user-Runs, gelesen aus den physischen Zeilen;
  • Absorbierte Display-Marker zählen nicht;
  • Unbekannte ids und der eigene Turn eines ungestempelten Carriers zählen mit — fail closed statt zu wenig zu zählen;
  • Bei einer Kürzung auf einem dauerhaften Carrier überleben die Zeilen vor dem Ziel.

Wenn Client und Server Turns unterschiedlich zählen, antwortet der Server mit 4033. Wenn der Client 4033 sieht:

  • Er rollt den optimistischen Zustand sofort zurück, da das abgelehnte Absenden nichts geschrieben hat;
  • Dann fragt er dich. Wenn du zustimmst, laufen regenerate oder edit von oben neu über die privaten runReload / runEdit, lesen die Sitzung neu und prüfen sie erneut;
  • Der erzwungene Deep Cut deckt nur das Transcript ab, für das du geantwortet hast. Jede Änderung währenddessen fragt erneut.

Der bereits vorhandene Stale-Target-Retry (4018) von Edit wurde ebenfalls verschärft: Er trägt die Deep-Bestätigung nur dann mit, wenn das aktualisierte Transcript dieselbe Anzahl späterer User-Turns hat.

Dieses Release warnt nur

Eines sollte klar sein: Diese Version blockiert Deep Cuts noch nicht. Sie protokolliert sie.

Wenn der Server eine unbestätigte Kürzung von mehr als einem User-Turn sieht, loggt er ALLOWED (warn-only release) und führt sie trotzdem aus. Der Durchsetzungs-Schalter ist DEEP_TRUNCATE_ENFORCED; sobald das nächste Release ihn setzt, wird dieselbe Kürzung mit 4033 abgelehnt und der Client geht den obigen Weg „zurückrollen, fragen, neu ausführen“.

Die Reihenfolge ist bewusst gewählt: ein Release mit echten Daten dazu, wie oft das fehlauslöst, dann das harte Gate — statt die Durchsetzung sofort auszuliefern und legitime tiefe Rewinds zu blockieren.

Status und Upgrade

Dieser Fix (PR #133748, schließt Issue #133716, als P0 markiert) wurde am 2026-10-11 nach main gemergt. Der aktuelle Tag v0.21.6 vom 8. Oktober enthält ihn nicht; das nächste Release wird es.

Nach dem Upgrade wirst du zwei Dinge bemerken: deutlich weniger versehentliche Kürzungen und gelegentlich eine zusätzliche Bestätigung. Genau dieser Dialog ist der ganze Sinn der Änderung — wenn er erscheint, bedeutet das, dass du kurz davor warst, eine Unterhaltung zu verlieren, die du noch nicht gelesen hattest.

Zum Schluss

Regenerate ist der Knopf, den Leute für „den letzten Schritt rückgängig machen“ halten, obwohl er eigentlich bedeutet: „ab einer bestimmten Zeile neu machen, und alles danach ist History“. Verwechselt man beides, kann ein einziger verirrter Klick einen ganzen Arbeitsabschnitt verschlucken. Die neue Regel ist klar: Wenn spätere Unterhaltung verloren gehen würde, zuerst fragen. Der Preis für den Schutz einer Sitzung ist ein Klick; der Preis für den Verlust von 107 Nachrichten ist, dass du nie erfährst, was darin stand.

Weiterlesen: Session-Resume und Fortsetzung behandelt das Wiederaufnehmen einer Unterhaltung, der Leitfaden zu Session-Grenzen erklärt, was als User-Grenze zählt, und Desktop-Embed-Isolation dreht sich darum, was in der UI passiert und was dir darüber gesagt wird.