hermes update hängt, lügt über den aktuellen Stand oder verweigert dein CA — vier Fixes sind gelandet

Es ist Freitagabend, und du tippst noch schnell hermes update, bevor du Feierabend machst. Der Bildschirm bleibt bei Fetching... stehen und bewegt sich zehn Minuten nicht. Du drückst Ctrl+C, probierst es auf einer anderen Maschine — und diesmal antwortet es sofort: Already up to date!, nur zeigt hermes --version nach dem Neustart immer noch das alte Release. Am unangenehmsten ist die dritte Variante: Da bewegt es den Code tatsächlich, meldet keinen Fehler, und du hältst dich für aktuell.
Diese drei Erlebnisse stammen aus vier separaten Bugs, die am 2026-09-28 alle in main gemergt wurden. Keiner davon berührt deine täglichen Gespräche, aber jeder bricht genau die Operation, die du brauchst, wenn es darauf ankommt. Hier steht, wodurch sie ausgelöst werden, was die Ursache war und was der Updater jetzt tut.
1. Shallow Checkouts: das Update hängt im Fetch, bis es in einen Timeout läuft
Der Installer klont mit --depth 1, um Zeit zu sparen — also nur die Historie des jüngsten Commits. Das ist völlig in Ordnung, bis der Checkout weit genug zurückfällt.
Bei einem Depth-1-Clone lässt ein einfaches git fetch origin main den Server die vollständige Abstammungslinie jedes Seitenzweigs mitschicken, der über die Shallow-Grenze hinaus gemergt wurde. Dieser Download kann innerhalb des 300-Sekunden-Limits für Netzwerkoperationen nicht fertig werden. Und der Unshallow-Schritt, der das behoben hätte, war nach einem erfolgreichen Update eingeplant — er lief also nie: Timeout, fehlgeschlagenes Update, kein Unshallow, beim nächsten Mal derselbe Timeout.
Jetzt läuft es andersherum: Vor dem begrenzten Fetch füllt heal_shallow_history den Commit-Graph auf (nur Commits, ein Budget von 900 Sekunden, und der Zielbranch wird mitgezogen), sodass der anschließende gedeckelte Fetch tatsächlich klein ausfällt.
Derselbe Patch-Satz behandelt zwei subtilere Fehlschläge:
index-pack --promisor-Crash in git 2.53+. Bei bestimmten lokalen Objekten läuftpack-objectsin ein internesBUG(). Der Schaden liegt darin, dass der Zustand im Repository bleibt, sodass jeder spätere Fetch — die Update-Prüfung und das Update selbst — daran stirbt. Der Fix ist bewusst eng gefasst: Bei genau dieser Crash-Signatur wird pro Aufruf einmal mit-c remote.origin.promisor=neu versucht, ohne die Repo-Konfiguration anzufassen.--filter=tree:0verwandelte volle Clones stillschweigend in partielle. Der Updater übergab diesen Filter bei jedem Tag-Fetch, undgit fetch --filterschreibt als Nebeneffektremote.origin.promisorundpartialclonefilter— ein gesunder voller Clone wurde so zu einem partiellen Clone, und jeder weitere Fetch zog eine Promisor-Abhängigkeit mit. Tag-Fetches übernehmen nun den eigenen Filter des Clones: Ein partieller Clone wiederholt seinen Filter, ein voller Clone fetcht ungefiltert.
Zwei Befehle verraten dir, ob deine Maschine von dieser Sorte ist:
git rev-parse --is-shallow-repository # true bedeutet shallow
git config --get remote.origin.partialclonefilter
2. Es meldete „Already up to date!“, während der Code gerade geändert wurde
Dieser ist fieser: Du denkst, es sei nichts passiert, dabei hat der Pull stattgefunden und Dependency-Sync, Config-Migration und Gateway-Neustart wurden alle übersprungen.
Ausgelöst wird das durch ein detached Checkout — etwa weil du vorher mit git checkout <tag> zurückgerollt bist. In diesem Zustand schaltete der Updater HEAD auf main um und zählte danach HEAD..origin/main. Dein lokales main zeigte bereits auf origin/main, die Zählung war also null: Es gab Already up to date! aus und beendete sich, obwohl der Code, den du tatsächlich ausgeführt hast, gerade ersetzt worden war. Die Zählung startet jetzt von dem Commit, der wirklich lief.
Der Rollback-Pfad war auf dieselbe Weise falsch. Wenn das Update mit einem Syntaxfehler endete, führte es git reset --hard auf dem Update-Branch aus: Ein detached Nutzer landete auf main geparkt, und die Commits eines geparkten Feature-Branches wurden auf main geschrieben. Jetzt wird in zwei Fälle unterschieden:
- detached Checkout:
git checkout --detach <sha>, zurück zu dem Commit, von dem das Update gestartet ist; - geparkter Branch:
git checkout <branch>, undmainbleibt unberührt. Hält ein anderer Worktree diesen Branch, wird sein Commit stattdessen detached wiederhergestellt.
Beide Fälle deckt eine Regel ab: Ein fehlgeschlagenes Update lässt deine Installation nie auf kaputtem Code sitzen.
3. Hinter einem Corporate-Netzwerk erreicht SSL_CERT_FILE git nie
Wenn deine Root-CA über die Umgebungsvariable SSL_CERT_FILE verteilt wird, bekommst du eine gespaltene Persönlichkeit: Die Versionsprüfung (Python) gelingt, und git fetch scheitert jedes Mal an TLS. Der Grund ist langweilig — das libcurl von git liest SSL_CERT_FILE nicht. Es respektiert nur GIT_SSL_CAINFO oder ein konfiguriertes http.sslCAInfo.
Der Fix bildet SSL_CERT_FILE einmal pro Update oder Prüfung auf GIT_SSL_CAINFO ab — in die Prozessumgebung, nicht in die Argumente eines einzelnen Befehls —, sodass auch die lazy Promisor-Fetches eines partiellen Clones die CA bekommen. Setzt du GIT_SSL_CAINFO explizit oder hast du http.sslCAInfo in deiner git config, gewinnt deine Einstellung.
Derselbe Patch-Satz lässt hermes pm install hinter einem internen npm-Mirror funktionieren, was zuvor scheiterte, wenn die im Lockfile festgeschriebene öffentliche URL nicht erreichbar war.
Deine zwei eigenen Checks:
echo "$SSL_CERT_FILE"
git config --get http.sslCAInfo
4. Fehlschläge, die als Erfolg verbucht werden
Die ersten drei sind „der Job wurde nicht erledigt“. Dieser hier ist „er wurde nicht erledigt, und man hat dir gesagt, er sei erledigt“ — genau das kostet dich den Abend.
Ein verweigerter .env-Schreibvorgang gab trotzdem ✓ Set aus. Wenn hermes config set Provider-Credentials speichert, läuft das über den .env-Schreibpfad, und dieser Pfad gab bei einem Lock-Konflikt früher nur eine Zeile aus und kehrte zurück. Der Aufrufer prüfte das Ergebnis nie: Er fuhr fort, die gespiegelten Werte aus config.yaml zu entfernen, gab ✓ Set aus und beendete sich mit 0 — du glaubtest, das Credential sei gespeichert. Jetzt rufen alle vier Multi-Store-Writer require_env_writable vor jedem Seiteneffekt auf: Die CLI beendet sich mit 1, PUT/DELETE /api/env antwortet mit 400 samt Grund, und das model.disconnect der TUI prüft jede Variable vorab.
Ein Dashboard, das nicht zurückkam, wurde als restarted verbucht. Während eines Updates übernimmt Hermes und startet ein Dashboard neu, das du von Hand gestartet hattest. Es spielte das /proc-argv des gestoppten Prozesses wörtlich erneut ab — inklusive eines Launcher-Pfads, den die Übernahme bereits in einen Shell-Shim umgeschrieben hatte —, sodass das Kind sofort mit SyntaxError starb. Popen selbst war erfolgreich, also gab der Updater trotzdem ✓ restarted aus. Das argv wird jetzt auf dem installationsgebundenen Launcher neu aufgebaut, den jeder andere Hermes-Spawn ebenfalls verwendet, ein Kind, das innerhalb einer Sekunde stirbt, gilt als fehlgeschlagener Neustart, und ein Dashboard, das nie zurückkommt, macht die ganze Update-Quittung zu partial mit Exit-Code 1.
Schauen, bevor du springst: zwei Read-only-Befehle
hermes update --check # gibt es eine neuere Version? (ändert nichts)
hermes update --plan # Read-only-Plan: Installationsart, jeder Hermes-Prozess über alle Profile, wie jeder neu startet
--plan ist der natürliche Begleiter zu diesen Fixes: Es gibt aus, was das Update anfassen wird, und ändert nichts, ist also auf einem Live-Host mit mehreren Profilen gefahrlos ausführbar. Die weiteren Flags, die man kennen sollte, sind --no-gateway-restart (Code tauschen, ohne das Gateway neu zu starten), --keep-stash und --backup / --no-backup.
Was du tun musst
Nichts. Alle vier Patches sind auf main; dein nächstes hermes update holt sie. Ist deine Maschine ein Shallow-Clone oder sitzt sie hinter einer Corporate-CA, widerstehe diesmal dem Impuls, Ctrl+C zu drücken — gib ihr Zeit, die Historie aufzufüllen. Danach ist hermes doctor ein guter Weg, um zu bestätigen, dass Dependencies, Config-Version und laufende Prozesse alle zusammenpassen.
Wenn der Upgrade-Ablauf selbst noch unscharf ist, starte mit unserem Upgrade-Walkthrough und mit dem Erhalt der Skills und Config deines Profils über ein Update hinweg. Gut zu wissen: Dasselbe Update ist auch das, was die Plugin-Kompatibilitätsschicht endgültig killt — importiert dein Plugin noch einen alten Pfad, erfährst du es direkt danach. Willst du stattdessen von Null aufsetzen? Der Installations-Guide deckt jede Plattform ab.