Una actualización llenó un agujero de 434 GB en el disco

Le das a Actualizar en el portátil, te vas a por un café y vuelves con un navegador que no abre, un editor quejándose del espacio en disco y el propio Hermes negándose a arrancar. El culpable resulta ser ~/.hermes/hermes-agent/.git, que pasó de aproximadamente un gigabyte a 434 GB y se comió un SSD de 1 TB.
Esto no fue «la actualización pesaba mucho». Una comprobación de versión en segundo plano disparó descargas bajo demanda sobre un diseño de instalación concreto, y la cosa fue a más. Aquí van el mecanismo, los números reales y cómo limpiarlo.
Una llamada a git que solo pregunta por una ruta
La app de escritorio de Hermes ejecuta una comprobación de consistencia al arrancar (internamente: bundle-skew). Pregunta si el código empaquetado en la app coincide con el código en disco y pide un reinicio si divergen. Lo hace con dos comandos de git:
git merge-base --is-ancestor <stamp> HEAD
git rev-list --count <stamp>..HEAD -- apps/desktop/...
Ambos son baratos en un clon normal. El problema es el diseño de la instalación: algunas instalaciones son clones parciales sin árboles (--filter=tree:0, los árboles se descargan bajo demanda en lugar de conservarse para cada commit histórico).
El segundo comando está filtrado por ruta (-- apps/desktop/...). Para contar cuántos commits de ese rango tocaron apps/desktop, git tiene que leer el árbol de cada commit del rango. Ninguno está en local, así que git recurre a la maquinaria promisor y los descarga por red, y cada descarga deja su propio paquete promisor en disco. Un historial más largo significa más árboles que descargar y más paquetes.
En el informe del incidente, ese rango abarcaba 1.784 commits.
De 1 GB a 434 GB: los números
Todo lo que viene a continuación procede del informe del issue #129514 (macOS, SSD de 1 TB):
| Elemento | Valor |
|---|---|
Crecimiento de .git |
~1,1 GB en un clon completo → 434 GB |
| Paquetes promisor | 2.659 (~398 GiB) |
Archivos tmp_pack_* abandonados |
1.309 (~29 GiB) |
| Paquete único más grande | 641 MB |
| Mantenimiento en segundo plano concurrente | 179 procesos en el pico (git maintenance run --auto → git commit-graph write --split --reachable), cada uno con ~2 GB de RSS |
| Carga del sistema | load average 220 |
| Cadencia de comprobación | un nuevo rev-list cada ~2 minutos tras el reinicio, cada uno ejecutándose 9-15 minutos |
| Resultado | la actualización falló con [Errno 28] No space left on device, el Mac se reinició y el CLI/TUI ya no arrancaba después |
Hubo brotes anteriores en el mismo informe: 138 paquetes promisor el 25 de septiembre, 11 el 27, 339 el 28, con un reinicio inesperado de por medio. No fue una sola ejecución desafortunada: esto se acumula.
Por qué «ya está arreglado» no te salva a mitad de actualización
El arreglo aterrizó el 28 de septiembre. Hoy la comprobación en main tiene este aspecto:
const options = {
cwd: repoRoot,
env: { ...process.env, GIT_NO_LAZY_FETCH: '1', GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'Never' },
timeoutMs: 5000
}
GIT_NO_LAZY_FETCH=1 significa no descargues objetos bajo demanda: falla en su lugar. Es exactamente la semántica que quiere esta comprobación, ya que nunca debería depender de la red. Súmale el timeout de 5 segundos y el reaping del árbol de procesos, y ya no puede arrastrar la máquina.
Pero hay un problema de sincronización, y es la parte más valiosa del informe:
El build que ejecuta la comprobación es el que ya está instalado, y solo se reemplaza al final del todo de la actualización.
La app de escritorio de quien reportó se compiló el 27 de septiembre a partir de 96cce68, un día antes del arreglo. En cuanto la actualización trajo 1.292 commits, la comprobación de la app antigua recorrió 96cce68..HEAD con el código viejo, sin GIT_NO_LAZY_FETCH. El disco se llenó, la actualización falló antes del paso que sustituye el nuevo build de escritorio, así que la app sin arreglar siguió instalada y reanudó el bucle tras el reinicio. No se cura sola.
Comprueba si te afecta
Cuatro comandos de solo lectura, unos segundos:
du -sh ~/.hermes/hermes-agent/.git # tamaño del almacén de objetos; ~1GB es normal
ls ~/.hermes/hermes-agent/.git/objects/pack/*.pack | wc -l # número de paquetes; uno o dos dígitos es normal
ls ~/.hermes/hermes-agent/.git/objects/pack/tmp_pack_* | wc -l # descargas sin activar; lo normal es 0
df -h ~ # espacio libre
Luego busca una cola de procesos de git y una carga anómala:
uptime
ps aux | grep -E '[r]ev-list|[g]it maintenance' | wc -l
Recuentos de paquetes en los cientos, un montón de tmp_pack_*, carga en las decenas: ese es el mismo patrón.
Cómo limpiarlo
Esta es una secuencia de limpieza genérica de git, no una lista de comandos oficial. Asegúrate de entender cada paso antes de ejecutarlo:
- Corta la hemorragia. Cierra la app de escritorio (si no, vuelve a dispararse cada dos minutos) y detén los procesos
git rev-list/git maintenanceen marcha. Mientras tanto, no arranques trabajo pesado en esa máquina. - Borra los restos sin activar. Los archivos
tmp_pack_*de.git/objects/pack/son descargas incompletas que git nunca referenciará; es seguro eliminarlos. En el caso reportado, solo eso recuperó unos 29 GiB. - Consolida el almacén de objetos. Ejecuta
git gc(precedido degit repack -adsi hace falta) para unir los paquetes dispersos. Ten en cuenta que gc sobre un repositorio con 2.659 paquetes es lento y devora CPU: hazlo con la máquina ociosa y vigiladf -h. - Actualiza el código desde la línea de comandos. El detalle clave: actualiza desde un terminal, no desde la app de escritorio previa al arreglo que causó el problema. Consulta la referencia de hermes update para ver los flags. Si tu instalación es un clon filtrado/shallow atascado en este estado, una instalación completa y limpia desde el canal oficial suele ser más rápida que desenredarlo.
- Confirma que el arreglo está aplicado. Después de actualizar,
~/.hermes/hermes-agent/apps/desktop/electron/bundle-skew.tsdebería contenerGIT_NO_LAZY_FETCH. Si no es así, sigues con el código antiguo. - Ponte un techo mental para el
.git. Alrededor de un gigabyte es sano; decenas de gigabytes significan que algo está tirando del historial a escondidas. No esperes a que el disco se llene primero.
En qué punto está esto
A fecha de 2026-10-01 el issue #129514 sigue abierto y etiquetado como P1. Es un seguimiento del #125932, que describía el mismo mecanismo en una fase más leve (decenas de procesos de git atascados). El arreglo en sí está en main —bundle-skew.ts ahora define GIT_NO_LAZY_FETCH=1 con un timeout de 5 segundos, y hermes_cli/_subprocess_compat.py incluye un NO_LAZY_FETCH_ENV equivalente—, pero la ruta de actualización desde un build previo al arreglo no tiene ninguna protección, que es el problema central que plantea el informe. Que los builds antiguos reciban una defensa adicional todavía no está decidido.
Si instalas desde un clon parcial, o pulsas Actualizar dentro de la app de escritorio, ejecuta hoy los pasos 1 y 2 de arriba.
La conclusión
Aquí no había ni una sola línea de código «mal». Chocaron dos decisiones razonables: usar un clon sin árboles para ahorrar disco y ejecutar una comprobación de consistencia de versión por seguridad, donde esa comprobación contiene un recorrido del historial filtrado por ruta que, en un repositorio sin árboles, significa «descargar todo este historial». Una vez arrancó la bola de nieve, el propio mantenimiento en segundo plano de git se sumó y se llevó por delante CPU, memoria y disco a la vez.
Merece la pena recordar tres señales: el tamaño de .git, el número de archivos de paquete y el recuento de restos tmp_pack_*. Las tres se leen en segundos y las tres te habrían avisado de que algo iba mal mucho antes de llenarse el disco. Las actualizaciones tienen otros modos de fallo: una actualización que se atasca pero informa de éxito y hacer una actualización con elegancia merecen ambas un vistazo.