hermes update paró los gateways y no los volvió a levantar: el .git que git mismo rechaza

En una máquina Windows, hermes update terminó y Telegram y WhatsApp dejaron de responder. Tres líneas en el log: tu gateway llamado hermes-2 estaba “pausado”, luego el fatal: not a git repository de git, y después una frase tranquilizadora — “Paused gateway(s) left stopped: the checkout HEAD is unreadable”, con la promesa de que el siguiente comando hermes devolvería el gateway.
Ejecutaste uno. No volvió nada. Ejecutaste hermes update otra vez: las mismas tres líneas, palabra por palabra.
El PR #142609, fusionado el 21 de octubre de 2026, arregla ese bucle. Vale la pena leerlo porque el bug no es “una función dejó de funcionar”, sino manejo de errores que sella el camino de recuperación. Esa clase de bug permanece invisible hasta que muerde, y cuando muerde asumes que rompiste algo tú.
Causa raíz: un .git que git mismo rechaza
Primero la premisa: este .git no falta, está presente y git no puede leerlo. Dos formas típicas:
.git/HEADestá vacío, o el archivo HEAD desapareció por completo..gites un archivo (no un directorio) que contienegitdir: <some path>, y esa ruta ya no existe — un resto habitual de un worktree o de una migración.
El updater de Hermes solo hace fetch cuando .git existe, así que hace fetch encantado contra este .git. Git responde not a git repository, la actualización falla. Hasta ahí, lo normal.
El problema real es el registro de pausa. Antes de actualizar, Hermes pausa los gateways en ejecución (para que nada lea el árbol mientras se mueve el código) y registra el estado del árbol. Ese registro tiene una comprobación llamada tree_is_whole y, siempre que HEAD era ilegible, mantenía el conjunto de pausados indefinidamente. Lo que significaba:
- Primera actualización: pausa el gateway, el fetch falla, HEAD ilegible, el conjunto se mantiene, el gateway sigue caído.
- Segunda actualización: el mismo HEAD ilegible, el conjunto se mantiene otra vez, el gateway sigue caído.
- Para siempre.
Una precondición rota (HEAD ilegible) se trataba como razón para no recuperarse — y nunca se iba a arreglar sola.
Arreglo uno: el registro de pausa suelta
La regla nueva: si toda pausa del conjunto ya encontró HEAD ilegible cuando se registró, y ningún paso ligó después un movimiento del árbol, el conjunto se reanuda y los gateways vuelven.
El razonamiento es simple. Los gateways estaban sirviendo exactamente ese árbol y nada escribió en él — sin estado a medio escribir que proteger, no hay razón para seguir aferrado.
Dos casos siguen manteniendo el conjunto, y esto importa si asumes que ahora libera sin condiciones:
- hay un movimiento de árbol ligado registrado (
_checkout_moveregistra targets y rutas antes de que git escriba nada); - HEAD era legible en la pausa y ahora no lo es — algo más dañó el árbol entretanto, y debería mirarlo una persona.
Arreglo dos: el error nombra el daño y la reparación
El segundo cambio está en el diagnóstico del fetch. Antes, cualquier causa producía Failed to fetch updates from origin., dejándote adivinar.
Ahora, cuando git mismo imprime fatal: not a git repository y el checkout falla rev-parse --show-toplevel, Hermes imprime:
This Hermes checkout's git data is damaged ...
seguido de un comando del instalador apuntado a este checkout — con la ruta:
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --dir <path>
En el lado de PowerShell es -InstallDir <path>. Eso importa para instalaciones en directorios personalizados: se reparan en el sitio en vez de ser empujadas de vuelta a la ubicación por defecto.
¿Por qué la comprobación extra con rev-parse? Porque un repositorio remoto con un .git colgando imprime la misma línea fatal: not a git repository. Coincidir solo con el texto diagnosticaría mal una rotura local, así que el veredicto depende de si el checkout actual se puede parsear o no. hermes update --check reporta el mismo diagnóstico.
Arreglo tres: el instalador maneja un archivo .git colgando
Ambos instaladores ya sabían mover a un lado, como .broken-<stamp>, un checkout cuyo HEAD es ilegible y clonar de cero. Pero install.sh solo lo hacía para un directorio .git; ante un archivo .git colgando se negaba con “not a Hermes git checkout”.
Ahora comprueba con -e, igual que el Test-Path de install.ps1. Ambos avisos dicen además “damaged .git”, en lugar de solo “interrupted clone” — antes los dos casos parecían idénticos.
Por qué el fallback ZIP sigue negándose
El updater tiene un fallback ZIP para cuando git no sirve: descargar un archivo y sobrescribir. En este escenario sigue negándose.
La razón está en la documentación: sin git status, no puede distinguir tus ediciones locales de un daño, y sobrescribir borraría el trabajo de alguien. Esa regla de fallar cerrado (#87304) se mantiene — mejor una reparación manual que una sobrescritura a ciegas.
Si estás atascado con esto ahora mismo
Si tienes delante un gateway parado, recórrelo en este orden (todo comportamiento de comandos ya publicados):
# 1. mira qué cree el gateway que está haciendo
hermes gateway list
hermes gateway status
# 2. si solo está pausado y nada más lo retiene, vuelve a levantarlo
hermes gateway restart
# 3. si el .git está realmente dañado, repáralo en el sitio con el instalador
# (sustituye <path> por la ruta de tu checkout)
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --dir <path>
Cuando estés en una build que lleve este arreglo, el paso 3 deja de ser tu trabajo — hermes update imprime el comando por ti. También jubila una idea equivocada relacionada: la promesa de que “el siguiente comando hermes reiniciará los gateways” no siempre era cierta, porque dependía de que el registro de pausa se liberara solo, y el código viejo nunca lo hacía.
Estado en las releases y radio de impacto
El PR se fusionó tarde el 21 de octubre, después de la publicación de v0.22.4 de ese día (18:49 UTC), así que no está en v0.22.4 ni en ninguna etiqueta — solo en main por ahora.
Los informes de campo son de Windows (.git/HEAD borrado mientras el árbol de trabajo quedaba), pero la causa raíz es neutral respecto a la plataforma: cualquier cosa que deje HEAD ilegible — limpiar .git a mano, un clon interrumpido, una migración que copió solo el worktree — dispara el mismo camino en Linux y macOS. Windows simplemente produce ese estado con más facilidad, por bloqueos de archivos y rarezas de permisos.
La lección es pequeña y general: cuando la lógica de recuperación depende de que una precondición se cure sola, y esa precondición es justo lo que está roto, el sistema se atasca en silencio. Ahora o se recupera, o te dice exactamente dónde reparar.