El directorio remoto de skills que se copia solo de vuelta a tu portátil

Ejecutas Hermes contra una máquina remota por SSH porque el trabajo tiene que ocurrir donde vive el código, y ha ido bien durante semanas. Luego te das cuenta de que ~/.hermes/skills/ en tu portátil es mucho más grande de lo que debería, y de que hay dentro un directorio .curator_backups que tú nunca creaste. Los números no son sutiles: en la instancia que lo reportó, 24 MB de backups antiguos del curator volvían en cada sync-back, y el .hub/lock.json del propio host fue reemplazado en silencio por una copia de la máquina remota.
Las dos mitades de la sincronización, y solo una recibió el filtro
Un backend de terminal SSH mantiene los archivos importantes sincronizados en ambas direcciones. A la ida sube tus skills, para que el agente remoto trabaje con las mismas skills que tú. A la vuelta, cuando se cierra la sesión, aplica todo lo que cambió en remoto.
La mitad de la subida es cuidadosa. Antes de recorrer el árbol de skills, poda un conjunto fijo de directorios que un agente remoto nunca lee:
.git .github .hub .archive .curator_backups .locks
.venv venv node_modules site-packages __pycache__
.tox .nox .pytest_cache .mypy_cache .ruff_cache
En tools/credential_files.py, _walk_skill_tree poda exactamente ese conjunto antes de descender, y el docstring explica por qué: esos árboles de contabilidad y de dependencias no son contenido de skills, así que recorrerlos costaría tiempo para nada. Esa poda es lo que evita que un node_modules de varios cientos de megabytes se envíe a un host remoto en cada sesión.
La mitad del sync-back no tiene un filtro equivalente. Y ese es el bug.
Por qué vuelven los archivos de más
Dos fragmentos de código de aspecto corriente se combinan mal:
_infer_host_pathmapea un archivo remoto de vuelta a una ruta del host haciendo coincidir su prefijo de directorio con archivos que ya conoce. Una ruta remota bajo un directorio conocido recibe simplemente la misma sustitución:/root/.hermes/skills/anything/xpasa a ser~/.hermes/skills/anything/x.- Entre los archivos que conoce hay algunos situados en la raíz misma del directorio de skills, como
.bundled_manifesty.usage.json. Eso es un resultado normal de la sincronización, y basta para convertir “el directorio de skills” en un prefijo mapeado.
Una vez que skills/ es un prefijo mapeado, todo lo que aparezca bajo él en el árbol de staging remoto se resuelve a una ruta del host, incluidos skills/.hub/lock.json y skills/.curator_backups/*, precisamente los árboles que la subida se saltó a propósito. Esos archivos nunca los subió esta sesión, así que no tienen hash registrado del push. _apply_staged_file usa exactamente ese hash para decidir si un archivo cambió:
pushed_hash = self._pushed_hashes.get(remote_path)
if pushed_hash is not None and _sha256_file(staged_file) == pushed_hash:
return 0 # unchanged from push
Sin hash no hay “evidencia de que no haya cambiado”, y el archivo se copia. En cada cierre de sesión. Y como las copias remotas no las pusiste tú, sino que las dejó una build más antigua de Hermes o Hermes ejecutándose de forma nativa en la máquina remota, pueden estar arbitrariamente obsoletas y ser arbitrariamente grandes.
El resumen de quien lo reportó es la versión más clara del impacto:
El
skills/.hub/lock.jsondel host se reemplaza por la copia remota, y los árboles remotos.curator_backupsynode_modulesse copian al directorio de skills del host. En nuestra instancia el remoto tenía 24 MB de backups antiguos del curator que volvían en cada sync-back.
Por qué debería importarte
Un .hub/lock.json reemplazado no es algo cosmético. Ese lock forma parte de cómo el hub de skills coordina lo que está instalado y lo que se está escribiendo; sobrescribirlo con la copia obsoleta de una máquina remota es cómo acabas con un directorio de skills que cree tener paquetes que no tiene, o que rechace trabajo que debería permitir. Los backups del curator son la memoria de un estado que decidiste dejar atrás, y que te los devuelvan en silencio, para siempre, es de esas cosas que descubres por el uso de disco y no por un mensaje de error.
Merece la pena señalar el alcance: la misma inferencia de rutas se ejecuta en todos los backends que descargan en bloque, no solo en SSH. SSH es simplemente donde se reportó primero, porque SSH es donde la gente mantiene un remoto persistente cuyo ~/.hermes lo escribió un Hermes distinto del que está en el portátil.
Revisa tu propia máquina
Mira antes de limpiar, en los dos lados:
ls -la ~/.hermes/skills/
du -sh ~/.hermes/skills/.curator_backups ~/.hermes/skills/.hub 2>/dev/null
En el host, .curator_backups es la pista: si existe y nunca ejecutaste un backup del curator en local, llegó por la red. .hub/lock.json es más difícil de juzgar a ojo; compara su hora de modificación con el último cierre de sesión y compáralo con la copia remota:
ssh your-remote 'ls -la ~/.hermes/skills/.hub/ ~/.hermes/skills/ | head -30'
La mitigación más limpia disponible hoy es eliminar los árboles problemáticos en el remoto, para que no quede nada obsoleto que copiar de vuelta. Lista primero y borra solo lo que reconozcas como duplicados antiguos, y hazlo cuando no tengas ninguna sesión activa en esa máquina:
ssh your-remote 'du -sh ~/.hermes/skills/.curator_backups 2>/dev/null; ls ~/.hermes/skills/.hub 2>/dev/null'
ssh your-remote 'rm -rf ~/.hermes/skills/.curator_backups ~/.hermes/skills/node_modules'
Si mantienes backups del curator en el remoto a propósito, conserva el más reciente y poda el resto en lugar de borrar el directorio.
Estado
Esto está registrado como issue #139364 (type/bug, backend/file-sync, P2), reportado el 15 de octubre de 2026 contra v0.21.7, con un test de regresión autocontenido en el pull request enlazado. El arreglo propuesto — PR #139365, keep skill bookkeeping trees out of sync-back — aplica la misma regla de exclusión al viaje de vuelta. A fecha de este artículo el PR está abierto, sin mergear, así que el comportamiento sigue en main.
Si usas el backend SSH con un remoto persistente, merece la pena dedicarle treinta segundos de comprobación sin importar cuándo llegue el arreglo: esos árboles no deberían estar ahí, y ahora ya sabes por qué aparecieron.
El mismo backend remoto ha dado otras sorpresas que conviene leer junto a esta: la corrección de la fuga de variables de entorno por SSH y la guía de OAuth a través de SSH.
Fuentes: la issue #139364 y su PR de arreglo enlazado #139365 en el repositorio de Hermes Agent; la exclusión del lado de la subida es _walk_skill_tree en tools/credential_files.py y la ruta de vuelta son _infer_host_path / _apply_staged_file en tools/environments/file_sync.py en origin/main; la lista de directorios excluidos es EXCLUDED_SKILL_DIRS en agent/skill_utils.py.