O diretório remoto de skills que se copia de volta para o seu notebook sem avisar

Você roda o Hermes contra uma máquina remota via SSH porque o trabalho precisa acontecer onde o código vive, e isso tem funcionado bem por semanas. Então você percebe que o ~/.hermes/skills/ do seu notebook está bem maior do que deveria, e que existe um diretório .curator_backups ali que você nunca criou. Os números não são sutis: na instância que reportou o problema, 24 MB de backups antigos do curator voltavam a cada sync-back, e o .hub/lock.json da própria máquina local era silenciosamente substituído por uma cópia da máquina remota.
As duas metades da sincronização, e só uma ganhou o filtro
Um backend de terminal SSH mantém os arquivos importantes em sincronia nos dois sentidos. Na ida, ele envia suas skills, para que o agente remoto trabalhe com as mesmas skills que você. Na volta — quando a sessão é desmontada — ele aplica tudo o que mudou do lado remoto.
A metade do envio é cuidadosa. Antes de percorrer a árvore de skills, ela poda um conjunto fixo de diretórios que um agente remoto nunca lê:
.git .github .hub .archive .curator_backups .locks
.venv venv node_modules site-packages __pycache__
.tox .nox .pytest_cache .mypy_cache .ruff_cache
Em tools/credential_files.py, _walk_skill_tree poda exatamente esse conjunto antes de descer na árvore, e a docstring explica o motivo: essas árvores de bookkeeping e de dependências não são conteúdo de skill, então percorrê-las custaria tempo sem retorno. É essa poda que evita que um node_modules de centenas de megabytes seja enviado a um host remoto a cada sessão.
A metade do sync-back não tem filtro equivalente. E esse é o bug.
Por que os arquivos extras voltam
Dois trechos de código aparentemente inofensivos se combinam mal:
_infer_host_pathmapeia um arquivo remoto de volta para um caminho local casando o prefixo de diretório dele com arquivos que já conhece. Um caminho remoto sob um diretório conhecido simplesmente recebe a mesma substituição —/root/.hermes/skills/anything/xvira~/.hermes/skills/anything/x.- Os arquivos que ele conhece incluem alguns que ficam no topo absoluto do diretório de skills, como
.bundled_manifeste.usage.json. Isso é um resultado normal da sincronização, e basta para transformar “o diretório de skills” num prefixo mapeado.
Assim que skills/ é um prefixo mapeado, tudo o que for encontrado na árvore de staging remota sob ele resolve para um caminho local — incluindo skills/.hub/lock.json e skills/.curator_backups/*, exatamente as árvores que o envio pulou de propósito. Esses arquivos nunca foram enviados por esta sessão, então eles não têm hash registrado do push. _apply_staged_file usa exatamente esse hash para decidir se um arquivo mudou:
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
Sem hash significa “sem evidência de que está inalterado”, e o arquivo é copiado. Em todo teardown. E como as cópias remotas não foram colocadas lá por você — foram deixadas por uma build mais antiga do Hermes ou pelo Hermes rodando nativamente na máquina remota — elas podem estar arbitrariamente desatualizadas e ser arbitrariamente grandes.
O resumo de quem reportou é a versão mais clara do impacto:
O
skills/.hub/lock.jsonda máquina local é substituído pela cópia remota, e as árvores.curator_backupsenode_modulesremotas são copiadas para o diretório de skills local. Na nossa instância, o remoto guardava 24 MB de backups antigos do curator que voltavam a cada sync-back.
Por que isso importa para você
Um .hub/lock.json substituído não é cosmético. Esse lock faz parte de como o hub de skills coordena o que está instalado e o que está sendo escrito; sobrescrevê-lo com a cópia obsoleta de uma máquina remota é o caminho para acabar com um diretório de skills que acredita ter pacotes que não tem, ou que recusa trabalho que deveria permitir. Backups do curator são a memória de um estado que você deixou para trás de propósito, e recebê-los de volta em silêncio, para sempre, é o tipo de coisa que você descobre pelo uso de disco, não por uma mensagem de erro.
Vale notar o escopo: a mesma inferência de caminho roda para todo backend que baixa em massa, não só o SSH. O SSH é apenas onde isso foi reportado primeiro, porque é onde as pessoas mantêm um remoto persistente cujo ~/.hermes foi escrito por um Hermes diferente do que está no notebook.
Cheque a sua própria máquina
Olhe antes de limpar, dos dois lados:
ls -la ~/.hermes/skills/
du -sh ~/.hermes/skills/.curator_backups ~/.hermes/skills/.hub 2>/dev/null
Na máquina local, .curator_backups é a pista — se ele existe e você nunca rodou um backup do curator localmente, ele chegou pela rede. Já o .hub/lock.json é mais difícil de julgar a olho; compare o horário de modificação dele com o último teardown da sua sessão e com a cópia remota:
ssh your-remote 'ls -la ~/.hermes/skills/.hub/ ~/.hermes/skills/ | head -30'
A mitigação mais limpa disponível hoje é remover as árvores problemáticas no remoto, para que não haja nada obsoleto a copiar de volta. Liste primeiro, e apague só o que você reconhece como duplicata antiga — e faça isso quando nenhuma sessão sua estiver ativa naquela 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'
Se você mantém backups do curator no remoto de propósito, guarde o mais recente e pode o resto em vez de apagar o diretório.
Status
Este caso está registrado como issue #139364 (type/bug, backend/file-sync, P2), reportado em 15 de outubro de 2026 contra a v0.21.7, com um teste de regressão autocontido no pull request vinculado. A correção proposta — PR #139365, keep skill bookkeeping trees out of sync-back — aplica a mesma regra de exclusão à viagem de volta. No momento em que escrevo, o PR está aberto, não foi integrado, então o comportamento continua na main.
Se você usa o backend SSH com um remoto persistente, isso vale trinta segundos de checagem, independentemente de quando a correção sair: essas árvores não deveriam estar aí, e agora você sabe por que elas apareceram.
O mesmo backend remoto já produziu outras surpresas que vale ler junto com esta: a correção do vazamento de ambiente via SSH e o guia de OAuth sobre SSH.
Fontes: issue #139364 e o PR de correção vinculado #139365 no repositório do Hermes Agent; a exclusão do lado do envio é _walk_skill_tree em tools/credential_files.py e o caminho de volta é _infer_host_path / _apply_staged_file em tools/environments/file_sync.py na origin/main; a lista de diretórios excluídos é EXCLUDED_SKILL_DIRS em agent/skill_utils.py.