Le répertoire de skills distant qui se recopie discrètement vers votre portable


Vous faites tourner Hermes sur une machine distante en SSH parce que le travail doit se faire là où vit le code, et tout va bien depuis des semaines. Puis vous remarquez que ~/.hermes/skills/ sur votre portable est bien plus gros qu’il ne devrait, et qu’il contient un répertoire .curator_backups que vous n’avez jamais créé. Les chiffres ne sont pas subtils : sur l’instance qui a signalé le problème, 24 Mo d’anciennes sauvegardes du curator revenaient à chaque synchronisation de retour, et le .hub/lock.json de l’hôte a été silencieusement remplacé par une copie venue de la machine distante.

Les deux moitiés de la synchro, et une seule a reçu le filtre

Un backend de terminal SSH maintient les fichiers importants alignés dans les deux sens. À l’aller, il envoie vos skills pour que l’agent distant travaille avec les mêmes skills que vous. Au retour — quand la session se démonte — il applique tout ce qui a changé côté distant.

La moitié montante est prudente. Avant de parcourir l’arborescence des skills, elle élague un ensemble fixe de répertoires qu’un agent distant ne lit jamais :

.git  .github  .hub  .archive  .curator_backups  .locks
.venv  venv  node_modules  site-packages  __pycache__
.tox  .nox  .pytest_cache  .mypy_cache  .ruff_cache

Dans tools/credential_files.py, _walk_skill_tree élague exactement cet ensemble avant de descendre, et la docstring explique pourquoi : ces arborescences de gestion et de dépendances ne sont pas du contenu de skill, les parcourir coûterait du temps pour rien. C’est cet élagage qui évite qu’un node_modules de plusieurs centaines de mégaoctets soit poussé vers un hôte distant à chaque session.

La moitié retour n’a pas de filtre équivalent. Et c’est là le bug.

Pourquoi les fichiers en trop reviennent

Deux morceaux de code d’apparence anodine se combinent mal :

  • _infer_host_path fait correspondre un fichier distant à un chemin hôte en comparant son préfixe de répertoire à des fichiers qu’il connaît déjà. Un chemin distant sous un répertoire connu reçoit simplement la même substitution — /root/.hermes/skills/anything/x devient ~/.hermes/skills/anything/x.
  • Les fichiers qu’il connaît incluent ceux situés tout en haut du répertoire de skills, comme .bundled_manifest et .usage.json. C’est un résultat ordinaire de la synchro, et cela suffit à faire du « répertoire de skills » un préfixe mappé.

Une fois que skills/ est un préfixe mappé, tout ce qui se trouve dans l’arborescence de staging distante sous ce préfixe se résout en un chemin hôte — y compris skills/.hub/lock.json et skills/.curator_backups/*, précisément les arborescences que l’envoi a délibérément ignorées. Ces fichiers n’ont jamais été envoyés par cette session : ils n’ont donc aucun hash enregistré par le push. _apply_staged_file utilise exactement ce hash pour décider si un fichier a changé :

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

Pas de hash signifie « aucune preuve qu’il est inchangé », et le fichier est copié. À chaque démontage. Et comme les copies distantes ne viennent pas de vous — elles ont été laissées par une version plus ancienne de Hermes ou par Hermes tournant nativement sur la machine distante — elles peuvent être arbitrairement périmées et arbitrairement volumineuses.

Le résumé du rapporteur est la version la plus claire de l’impact :

Le skills/.hub/lock.json de l’hôte est remplacé par la copie distante, et les arborescences .curator_backups et node_modules distantes sont copiées dans le répertoire de skills de l’hôte. Sur notre instance, le distant contenait 24 Mo d’anciennes sauvegardes du curator qui revenaient à chaque synchronisation de retour.

Pourquoi cela doit vous intéresser

Un .hub/lock.json remplacé n’est pas cosmétique. Ce verrou fait partie du mécanisme par lequel le hub de skills coordonne ce qui est installé et ce qui est en cours d’écriture ; l’écraser avec la copie périmée d’une machine distante, c’est la façon dont on obtient un répertoire de skills qui croit avoir des paquets qu’il n’a pas, ou qui refuse du travail qu’il devrait accepter. Les sauvegardes du curator sont la mémoire d’un état que vous avez délibérément dépassé, et se les voir rendre silencieusement, pour toujours, est le genre de chose que l’on découvre par l’occupation disque plutôt que par un message d’erreur.

Cela vaut la peine de préciser la portée : la même inférence de chemin s’exécute pour tout backend qui télécharge en masse, pas seulement SSH. SSH est simplement là où le problème a été signalé en premier, parce que c’est là que les gens gardent un distant persistant dont le ~/.hermes a été écrit par un autre Hermes que celui du portable.

Vérifiez votre propre machine

Regardez avant de nettoyer, des deux côtés :

ls -la ~/.hermes/skills/
du -sh ~/.hermes/skills/.curator_backups ~/.hermes/skills/.hub 2>/dev/null

Sur l’hôte, .curator_backups est l’indice — s’il existe et que vous n’avez jamais lancé de sauvegarde du curator en local, il est arrivé par le réseau. .hub/lock.json est plus difficile à juger à l’œil ; comparez son heure de modification à votre dernier démontage de session, et comparez-le à la copie distante :

ssh your-remote 'ls -la ~/.hermes/skills/.hub/ ~/.hermes/skills/ | head -30'

La mitigation la plus propre disponible aujourd’hui consiste à supprimer les arborescences fautives sur le distant, pour qu’il ne reste rien de périmé à recopier. Listez d’abord, puis ne supprimez que ce que vous reconnaissez comme d’anciens doublons — et faites-le quand aucune de vos sessions n’est active sur cette machine :

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 vous conservez volontairement des sauvegardes du curator sur le distant, gardez la plus récente et élaguez le reste plutôt que de supprimer le répertoire.

Statut

Le problème est suivi comme issue #139364 (type/bug, backend/file-sync, P2), signalée le 15 octobre 2026 contre la v0.21.7, avec un test de régression autonome dans la pull request liée. Le correctif proposé — PR #139365, keep skill bookkeeping trees out of sync-back — applique la même règle d’exclusion au trajet retour. À l’heure où nous écrivons, la PR est ouverte, non fusionnée, donc le comportement est toujours sur main.

Si vous utilisez le backend SSH avec un distant persistant, cela vaut trente secondes de vérification quelle que soit la date d’arrivée du correctif : les arborescences ne devraient pas être là, et vous savez maintenant pourquoi elles sont apparues.

Le même backend distant a produit d’autres surprises à lire en parallèle : la correction de la fuite de variables d’environnement en SSH et le guide OAuth via SSH.


Sources : l’issue #139364 et sa PR de correction liée #139365 dans le dépôt Hermes Agent ; l’exclusion côté envoi est _walk_skill_tree dans tools/credential_files.py et le chemin retour est _infer_host_path / _apply_staged_file dans tools/environments/file_sync.py sur origin/main ; la liste des répertoires exclus est EXCLUDED_SKILL_DIRS dans agent/skill_utils.py.