hermes update hat die Gateways pausiert und nie wieder geweckt: das .git, das git selbst ablehnt


Auf einer Windows-Kiste lief hermes update durch, und danach antworteten Telegram und WhatsApp nicht mehr. Drei Zeilen im Log: Dein Gateway namens hermes-2 wurde „pausiert“, dann fatal: not a git repository von git, dann eine Beruhigung — „Paused gateway(s) left stopped: the checkout HEAD is unreadable“, mit dem Versprechen, dass der nächste hermes-Befehl das Gateway zurückbringt.

Du hast einen ausgeführt. Nichts kam zurück. Du hast hermes update erneut gestartet: dieselben drei Zeilen, Wort für Wort.

PR #142609, gemergt am 21. Oktober 2026, behebt diese Schleife. Es lohnt das Lesen, weil der Bug nicht „ein Feature funktioniert nicht mehr“ ist — es ist Fehlerbehandlung, die den Reparaturweg abriegelt. Diese Sorte Bug bleibt unsichtbar, bis sie zubeißt, und wenn sie zubeißt, glaubst du, du hättest etwas kaputt gemacht.

Ursache: ein .git, das git selbst ablehnt

Zuerst die Prämisse: Dieses .git fehlt nicht, es ist da und für git unlesbar. Zwei typische Formen:

  • .git/HEAD ist leer, oder die HEAD-Datei fehlt komplett.
  • .git ist eine Datei (kein Verzeichnis) mit dem Inhalt gitdir: <ein Pfad>, und dieser Pfad existiert nicht mehr — ein häufiger Rest eines Worktrees oder einer Migration.

Der Updater von Hermes holt nur, wenn .git existiert, also holt er fröhlich gegen dieses .git. Git antwortet not a git repository, das Update scheitert. So weit alles normal.

Das eigentliche Problem ist der Pause-Eintrag. Vor dem Update pausiert Hermes laufende Gateways (damit nichts den Tree liest, während Code wandert) und schreibt den Zustand des Trees fest. Dieser Eintrag hat eine Prüfung namens tree_is_whole, und wann immer HEAD unlesbar war, hielt sie die pausierte Menge unbegrenzt fest. Das bedeutete:

  1. Erstes Update: Gateway pausieren, Fetch scheitert, HEAD unlesbar, pausierte Menge gehalten, Gateway bleibt unten.
  2. Zweites Update: derselbe unlesbare HEAD, Menge erneut gehalten, Gateway weiter unten.
  3. Für immer.

Eine kaputte Vorbedingung (HEAD unlesbar) wurde als Grund behandelt, nicht zu recovern — und sie würde sich nie selbst reparieren.

Fix eins: Der Pause-Eintrag lässt los

Die neue Regel: Wenn jede Pause in der Menge bereits beim Aufzeichnen einen unlesbaren HEAD gefunden hat und danach kein Schritt einen Tree-Move gebunden hat, wird die Menge fortgesetzt und die Gateways kommen zurück.

Die Begründung ist einfach. Die Gateways haben genau diesen Tree ausgeliefert, und nichts hat hineingeschrieben — ohne halb geschriebenen Zustand, den man schützen müsste, gibt es keinen Grund, weiter festzuhalten.

Zwei Fälle halten die Menge weiterhin, und das zählt, wenn du annimmst, sie gebe jetzt bedingungslos frei:

  • ein gebundener Tree-Move ist aufgezeichnet (_checkout_move schreibt Ziele und Pfade, bevor git irgendetwas schreibt);
  • HEAD war beim Pausieren lesbar und ist es jetzt nicht — etwas anderes hat den Tree dazwischen beschädigt, und ein Mensch sollte nachsehen.

Fix zwei: Der Fehler nennt Schaden und Reparatur

Die zweite Änderung steckt in der Fetch-Diagnose. Vorher produzierte jede Ursache Failed to fetch updates from origin. und ließ dich raten.

Jetzt gilt: Wenn git selbst fatal: not a git repository druckt und der Checkout rev-parse --show-toplevel nicht besteht, gibt Hermes aus:

This Hermes checkout's git data is damaged ...

gefolgt von einem Installer-Befehl, der auf diesen Checkout zeigt — mit dem Pfad:

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --dir <path>

Auf der PowerShell-Seite ist es -InstallDir <path>. Das zählt für Installationen in eigenen Verzeichnissen: Sie werden an Ort und Stelle repariert, statt in den Standardpfad zurückgedrängt zu werden.

Warum die zusätzliche rev-parse-Prüfung? Weil ein Remote-Repository mit einem baumelnden .git dieselbe Zeile fatal: not a git repository druckt. Nur auf den Text zu matchen würde einen lokalen Schaden falsch diagnostizieren, deshalb hängt das Urteil davon ab, ob der aktuelle Checkout überhaupt parsebar ist. hermes update --check meldet dieselbe Diagnose.

Fix drei: Der Installer kommt mit einer baumelnden .git-Datei klar

Beide Installer wussten bereits, wie man einen Checkout mit unlesbarem HEAD als .broken-<stamp> beiseiteschiebt und frisch klont. Aber install.sh tat das nur für ein .git-Verzeichnis; bei einer baumelnden .git-Datei lehnte es mit „not a Hermes git checkout“ ab.

Jetzt testet es mit -e, passend zu Test-Path in install.ps1. Beide Warnungen sagen außerdem nun „damaged .git“ statt nur „interrupted clone“ — die beiden sahen früher identisch aus.

Warum der ZIP-Fallback weiterhin verweigert

Der Updater hat einen ZIP-Fallback für den Fall, dass git unbrauchbar ist: Archiv herunterladen und überschreiben. In diesem Szenario verweigert er weiterhin.

Der Grund steht in den Docs: Ohne git status kann er deine lokalen Änderungen nicht von einem Schaden unterscheiden, und Überschreiben würde jemandes Arbeit löschen. Diese Fail-closed-Regel (#87304) bleibt bestehen — lieber manuell reparieren als geraten überschreiben.

Wenn du gerade jetzt feststeckst

Wenn du auf ein gestopptes Gateway starrst, arbeite es in dieser Reihenfolge ab (alles Verhalten ausgelieferter Befehle):

# 1. schauen, was das Gateway zu tun glaubt
hermes gateway list
hermes gateway status

# 2. wenn es nur pausiert ist und sonst nichts es hält, zurückholen
hermes gateway restart

# 3. wenn .git wirklich beschädigt ist, an Ort und Stelle mit dem Installer reparieren
#    (<path> durch deinen Checkout-Pfad ersetzen)
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --dir <path>

Sobald du auf einem Build mit diesem Fix bist, ist Schritt 3 nicht mehr deine Aufgabe — hermes update druckt den Befehl für dich. Damit ist auch ein verwandtes Missverständnis erledigt: Das Versprechen, dass „der nächste hermes-Befehl die Gateways neu startet“, stimmte nicht immer, denn es hing davon ab, dass der Pause-Eintrag sich selbst freigibt, und der alte Code tat das nie.

Release-Status und Reichweite

Der PR wurde spät am 21. Oktober gemergt, nach der Veröffentlichung von v0.22.4 an diesem Tag (18:49 UTC), ist also nicht in v0.22.4 und in keinem Tag — vorerst nur auf main.

Die Feldmeldungen kommen von Windows (.git/HEAD gelöscht, während der Working Tree blieb), aber die Ursache ist plattformneutral: Alles, was HEAD unlesbar macht — Handreinigung von .git, ein unterbrochener Clone, eine Migration, die nur den Worktree kopiert hat — löst unter Linux und macOS denselben Pfad aus. Windows erzeugt diesen Zustand nur leichter, durch Datei-Locks und Berechtigungs-Eigenheiten.

Die Lehre ist klein und allgemein: Wenn die Reparaturlogik davon abhängt, dass sich eine Vorbedingung selbst heilt, und genau diese Vorbedingung das Kaputte ist, bleibt das System still stehen. Jetzt recovered es entweder, oder es sagt dir genau, wo du reparieren musst.