"Chat out of date": wenn der Desktop jede Nachricht verweigert


Du arbeitest seit Stunden in einer Desktop-Sitzung, ein Tool-Aufruf nach dem anderen. Du drückst Enter und bekommst „Chat out of date“ zu sehen — das Fenster sei hinter einer anderen Ansicht desselben Chats zurückgeblieben, die neuesten Nachrichten seien geladen, sende erneut, wenn du es weiterhin willst. Du sendest erneut. Dieselbe Meldung. Ein Neustart der App ändert nichts. Von deinen Daten fehlt nichts, aber diese Unterhaltung kann keine neue Nachricht mehr annehmen.

PR #125523, gemergt am 2026-09-27, behebt die Schleife. Issue #123909 enthält die vollständige Timeline der Reproduktion.

Das Symptom: eine Verweigerungsschleife, die nicht konvergieren kann

  • Ein Auslöser wirkt völlig harmlos: eine Sitzung in ein Projekt verschieben. Nach dem Verschieben friert der In-Memory-Store des Fensters an der Grenze von vor der Verschiebung ein, und ab da wird jeder Versand clientseitig blockiert.
  • Es war kein Einzelfall: #123033 und #123856 meldeten denselben Guard, ohne dass überhaupt ein Auslöser erkennbar war.
  • Es gibt ein zweites, leiseres Opfer: Das Forken eines betroffenen Chats setzt den Fork aus diesem eingefrorenen Store auf und lässt damit alles nach der Verschiebung still fallen. Kein Fehler — die neue Sitzung enthält diesen Verlauf einfach nicht.

Ursache: zwei Konsumenten, uneinig über einen Store

Zwei Komponenten lesen denselben Sitzungs-Store und hatten unterschiedliche Vorstellungen davon, was „aktuell“ bedeutet:

  • Retention (transcript-retention.ts, eingeführt für #77311) gibt bewusst den Kopf des Stores frei und behält das Live-Fenster plus eine Gewichtsseite Reserve. Jedes ChatMessage.parts trägt gerenderte Tool-Ausgabe, Datei-Vorschauen, Diffs und Bilder — so wächst ein langlebiges Fenster. Ein tool-lastiges Koordinator-transcript verbrennt dieses Budget schnell, sodass der getrimmte Store deutlich weniger gerenderte Nachrichten hält, als die Sitzung tatsächlich hat.
  • Der alte Guard (stale-transcript-guard.ts) verglich Zählerstände: Die neueste Seite mit 120 Zeilen (includeCompacted: true) faltete sich auf, sagen wir, 85 Chat-Nachrichten zusammen — mehr, als der getrimmte Store hielt — also galt die Ansicht als hinterher, der Versand wurde verweigert und die neu geladene Seite installiert. Die nächste Verschiebung des Fensterankers trimmte erneut, und der nächste Versand wurde erneut verweigert. Von Konstruktion her nicht konvergent, und sie überlebte Neuladen wie Neustart, weil die Hydration erneut trimmt, sobald der Fensterschnitt etabliert ist.

Kurze Chats überschreiten dieses Budget nie. Deshalb traf es nur lange, schwere Unterhaltungen — einschließlich des kanonischen Bot Chat, der von Design her ein Forever-Chat ist.

Der Fix: das Ende vergleichen, nicht die Summe

Die neue Logik ändert, was verglichen wird:

  • Ein neues lastDurableRowId läuft vom Ende der Ansicht rückwärts bis zur ersten Nachricht mit einer persistierten rowId — dem Ende der Ansicht. Retention kann nur jemals den Kopf freigeben, also ist die letzte durable Zeile immer das lebende Ende; nicht persistierte Zeilen (optimistische Prompts, streamende Antworten) liegen dahinter.
  • Wenn die letzte durable rowId der lokalen Ansicht gleich der letzten durablen rowId der Seite ist, die das Backend gerade zurückgegeben hat, ist die Ansicht aktuell — egal wie viel vom Kopf die Retention herausgegeben hat. Ein Peer-Fenster mit wirklich neueren Zeilen verschiebt die Spitze weiterhin, echte Veraltung wird also nach wie vor abgewiesen.
  • Eine Quelle von False Positives fiel damit weg: vom Backend geschriebene systemNotice-Zeilen werden auf der Timeline gerendert, gehören aber zu keiner Ansicht; zählt man sie mit, meldet man ein zweites Fenster, das gar nicht existiert — ein In-Place-Modellwechsel allein reichte, damit jeder Versand als hinterher erschien.

Was du tun kannst

  • Upgraden ist die direkte Antwort. Sobald der Fix in deiner Installation steckt, werden lange Chats nicht mehr von dieser Schleife abgewiesen (der Desktop folgt hermes update).
  • Auf einem Rechner, der noch nicht aktualisiert wurde, ist der praktische Workaround bei dieser Meldung: in einer neuen Sitzung weitermachen oder von der CLI aus weiterarbeiten (hermes sessions browse lässt dich eine vergangene Sitzung auswählen und fortsetzen). Nicht immer wieder auf Senden drücken.
  • Falls du wegen dieses Problems bereits eine Sitzung geforkt hast: Behandle diesen Fork als unzuverlässige Kopie. Die ursprüngliche Sitzung existiert weiterhin mit ihrem vollständigen Verlauf, verwende den Fork also nicht als Backup.
  • Ein weiterer Fix für lange Unterhaltungen landete am selben Tag und ist als Nächstes lesenswert: warum die Kompaktierung mitten im Turn den Verlauf nicht umschreiben darf. Zur Desktop-App selbst siehe die Interface-Modi und Hermes Desktop in unserem Ecosystem-Bereich.

Release-Status

Der Patch wurde am 2026-09-27 gemergt, nach dem Release-Tag v0.21.5 (v2026.9.24), ist also vorerst nur auf main.

Das Fazit

Die Lehre hier steckt in keiner einzelnen Codezeile: Beide Module waren „korrekt“. Das eine gibt Verlauf nach Gewicht frei, um den Speicher zu begrenzen; das andere beurteilte die Aktualität über das Zählen von Zeilen — zusammengesetzt konnte der Vergleich nie gelingen. Der Fix justiert das Budget nicht. Er wählt ein Faktum, über das beide Seiten einig sind: die letzte durable Zeile. Stimmen die Spitzen überein, ist die Ansicht aktuell; stimmen sie nicht, ist sie wirklich hinterher. Der Speicher wird weiterhin begrenzt, und das Senden funktioniert wieder.