Por que 90 GB do seu disco desapareceram: o estado de dependências deixado para trás por worktrees excluídos

Seu laptop diz que o disco está quase cheio. Você limpa a pasta de downloads, remove algumas imagens Docker, reinicia, e duas semanas depois ele diz a mesma coisa. O verdadeiro culpado é um diretório que você provavelmente nunca abriu: ~/.hermes/installs. Ele nunca aparece como um processo grande e nunca surge no du -sh ~/Downloads — apenas cresce em silêncio.
O motivo é simples e não é um bug. Toda vez que você inicia o agente em um worktree de hermes -w, uma árvore de review ou um clone descartável, o Hermes prepara um ambiente de dependências dedicado para aquele checkout — um ambiente Python, um ambiente de teste, o runtime do PM, cerca de 200 MB por árvore. Quando a árvore desaparece, esse ambiente não. O PR #133808, mesclado em 7 de outubro de 2026, fecha essa lacuna.
O estado é indexado por caminho
Primeiro, o que a coisa de fato é. O estado de dependências vive em <hermes home>/installs/<install_key>/, onde install_key não é um nome de projeto, mas um hash do caminho:
# pm/environments.py
def install_key(project_root: Path) -> str:
canonical = str(Path(project_root).resolve())
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]
Então dois worktrees do mesmo repositório ganham chaves diferentes e ambientes diferentes, porque seus caminhos diferem. Isso é proposital: todo o sentido de um worktree é o isolamento, e compartilhar um ambiente de dependências entre árvores o consumiria.
O custo é que o isolamento tem prazo de validade. git worktree remove, o pruner de inicialização, hermes worktree prune e até um rm -rf manual na árvore deixam installs/<key>/ intocado. O estado de dependências existe por caminho de checkout; o checkout sumiu, o estado não, e nada mais o referencia.
Os números no PR tornam a escala concreta. Em um host de desenvolvedor, após quinze dias de campanhas de worktree, ~/.hermes/installs continha 499 entradas totalizando 92 GB, das quais 402 entradas (80 GB) registravam um checkout que não existe mais — 360 delas sob .worktrees/ — com idade mediana de quatro dias. Apenas 76 entradas pertenciam a checkouts vivos. Mais de quatro quintos do espaço era estado fantasma.
A correção: um registro decide o que é órfão
O novo pm/install_states.py faz algo deliberadamente simples. Todo diretório de estado que sincronizou com sucesso contém inputs/.project-root, escrito por record_activation_inputs, nomeando o checkout ao qual pertence. A verificação é simplesmente se esse caminho ainda está lá.
A parte sutil é que “não está lá” não significa “excluído”. Uma raiz de dados compartilhada com um contêiner (-v ~/.hermes:/opt/data) ou um volume não montado também registrará caminhos que o processo atual não consegue ver. Então o veredito é restrito a comprovadamente excluído: o checkout precisa ter ficado em algum lugar que este processo consegue observar — sob a raiz de dados que é dona de installs/, ou diretamente dentro de um diretório .worktrees/ que ainda existe. Diretórios de estado sem nenhum registro nunca são tocados.
A recuperação é igualmente conservadora. Tudo que ainda está retido é mantido:
- o
.install.locknão pode ser obtido (um processo está usando); - qualquer geração sob
environments/ainda tem um lease vivo (algo está lendo); - um lock que nem consegue ser aberto conta como retido.
Esse viés é correto. Excluir um ambiente de dependências em uso quebra a sessão em execução; manter um que deveria ter ido embora custa apenas disco.
Como recuperar isso agora
Não há migração a executar. Dois caminhos fazem o trabalho automaticamente:
hermes pm gc # a saída ganha uma entrada final: N install state dir(s) of deleted checkouts
hermes worktree prune # recupera o estado depois da passagem de branch, para árvores que sumiram
O pruner que roda a cada inicialização do Hermes também faz isso, então no uso normal o estado de um worktree excluído desaparece no próximo início do hermes ou no próximo pm gc. A recuperação na inicialização é fail-soft — uma falha é registrada em nível de debug e nunca bloqueia a inicialização.
Para ver o que você está carregando primeiro:
du -sh ~/.hermes/installs # total
ls ~/.hermes/installs | wc -l # número de entradas
hermes worktree list # idade, tamanho, veredito e motivo de cada árvore em .worktrees/
O hermes worktree list é o pré-voo seguro: ele imprime um veredito e o motivo para cada árvore antes de você fazer o prune. Ele nunca exclui alterações rastreadas não commitadas, commits únicos ainda não enviados (unpushed) ou árvores em uso, e árvores descartáveis apenas com arquivos não rastreados são arquivadas em ~/.hermes/archive/worktree-prune/ antes da remoção.
Como isso difere das outras histórias de disco
Já cobrimos alguns problemas relacionados a disco aqui, e os mecanismos são diferentes o suficiente para manter separados:
- uma atualização estourando o disco é sobre artefatos temporários durante o
hermes update; - a perda de dados do worktree sujo é sobre um pruner que excluiu árvores que deveria ter mantido;
- este é a direção oposta: algo que deveria ter sido excluído não foi. A limpeza alcançou a árvore mas não o estado de dependências que a árvore deixou para trás.
Os dois primeiros são bugs de guardrail; este é uma lacuna de cobertura. Juntos eles formam o quadro do lado das dependências, e reduzir o armazenamento a 78% do tamanho anterior cobre o lado dos dados — sessões e caches — se você quiser as duas metades.
A versão curta
~/.hermes/installs/armazena o estado de dependências indexado pelo hash do caminho do checkout, um ambiente de ~200 MB por worktree ou clone descartável;- excluir a árvore nunca excluía esse estado — nem
pm gc, nem o worktree prune, nemrm -rf; - agora
hermes pm gc,hermes worktree prunee o pruner de inicialização recuperam o estado cujo checkout foi comprovadamente excluído, e deixam intacto tudo que estiver atrás de um lock ou lease retido; - medido em um host: 499 entradas em 92 GB, 402 delas órfãs (80 GB), 92 GB reduzidos a 18 GB após a recuperação.
Se você abre worktrees ou cria clones descartáveis com frequência, rode um du -sh ~/.hermes/installs hoje. Se o número for maior do que você esperava, o hermes pm gc é a resposta completa.