Das entfernte Skills-Verzeichnis, das sich still auf deinen Laptop zurückkopiert


Du betreibst Hermes über SSH gegen eine entfernte Maschine, weil die Arbeit dort passieren muss, wo der Code liegt, und wochenlang lief alles bestens. Dann fällt dir auf, dass ~/.hermes/skills/ auf deinem Laptop deutlich größer ist als es sein sollte, und darin liegt ein .curator_backups-Verzeichnis, das du nie angelegt hast. Die Zahlen sind nicht subtil: Auf der Instanz, die das gemeldet hat, kamen bei jedem einzelnen Sync-Back 24 MB alte Curator-Backups zurück, und die .hub/lock.json des Hosts wurde still durch eine Kopie von der entfernten Maschine ersetzt.

Die zwei Hälften des Syncs, und nur eine bekam den Filter

Ein SSH-Terminal-Backend hält die wichtigen Dateien in beide Richtungen synchron. Hinaus lädt es deine Skills hoch, damit der entfernte Agent mit denselben Skills arbeitet wie du. Zurück — wenn die Session abgebaut wird — übernimmt es alles, was sich remote geändert hat.

Die Upload-Hälfte ist sorgfältig. Bevor sie den Skills-Baum durchläuft, beschneidet sie eine feste Menge von Verzeichnissen, die ein entfernter Agent nie liest:

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

In tools/credential_files.py beschneidet _walk_skill_tree genau diese Menge, bevor es absteigt, und der Docstring nennt den Grund: Diese Bookkeeping- und Dependency-Bäume sind kein Skill-Inhalt, sie zu durchlaufen würde also nur Zeit kosten. Genau dieses Pruning verhindert, dass bei jeder Session ein mehrere hundert Megabyte großes node_modules auf einen entfernten Host geschoben wird.

Die Sync-Back-Hälfte hat keinen entsprechenden Filter. Und das ist der Bug.

Warum die zusätzlichen Dateien zurückkommen

Zwei Stücke harmlos aussehenden Codes ergänzen sich schlecht:

  • _infer_host_path bildet eine entfernte Datei auf einen Host-Pfad ab, indem es ihr Verzeichnis-Präfix gegen bereits bekannte Dateien matcht. Ein entfernter Pfad unterhalb eines bekannten Verzeichnisses bekommt einfach dieselbe Substitution — aus /root/.hermes/skills/anything/x wird ~/.hermes/skills/anything/x.
  • Zu den Dateien, die es kennt, gehören auch solche ganz oben im Skills-Verzeichnis, etwa .bundled_manifest und .usage.json. Das ist ein normales Ergebnis des Syncs und reicht aus, um „das Skills-Verzeichnis“ zu einem gemappten Präfix zu machen.

Sobald skills/ ein gemapptes Präfix ist, löst sich alles, was im entfernten Staging-Baum darunter gefunden wird, in einen Host-Pfad auf — einschließlich skills/.hub/lock.json und skills/.curator_backups/*, genau der Bäume, die der Upload bewusst übersprungen hat. Diese Dateien wurden von dieser Session nie hochgeladen, sie haben also keinen aufgezeichneten Hash aus dem Push. _apply_staged_file nutzt genau diesen Hash, um zu entscheiden, ob sich eine Datei geändert hat:

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

Kein Hash bedeutet „kein Beleg, dass die Datei unverändert ist“, und die Datei wird kopiert. Bei jedem Teardown. Und weil die entfernten Kopien nicht von dir stammen — sie wurden von einem älteren Hermes-Build oder von nativ auf der entfernten Maschine laufendem Hermes hinterlassen —, können sie beliebig veraltet und beliebig groß sein.

Die Zusammenfassung des Melders ist die klarste Version der Auswirkung:

The host’s skills/.hub/lock.json is replaced by the remote copy, and the remote .curator_backups and node_modules trees are copied into the host’s skills directory. On our instance the remote held 24 MB of old curator backups that came back on every sync-back.

Warum dich das interessieren sollte

Eine ersetzte .hub/lock.json ist nicht kosmetisch. Dieses Lock gehört dazu, wie der Skills-Hub koordiniert, was installiert ist und was gerade geschrieben wird; es mit einer veralteten Kopie einer entfernten Maschine zu überschreiben, ist der Weg zu einem Skills-Verzeichnis, das glaubt, Pakete zu haben, die es gar nicht hat, oder das Arbeit verweigert, die es zulassen sollte. Curator-Backups sind die Erinnerung an einen Zustand, den du bewusst hinter dir gelassen hast, und sie still und für immer zurückgereicht zu bekommen, ist die Art von Problem, die man über den Speicherverbrauch entdeckt und nicht über eine Fehlermeldung.

Erwähnenswert ist auch der Umfang: Dieselbe Pfad-Inferenz läuft für jedes Backend, das in Bulk herunterlädt, nicht nur für SSH. SSH ist nur der Ort, an dem es zuerst gemeldet wurde, weil SSH der Ort ist, an dem Leute ein dauerhaftes Remote halten, dessen ~/.hermes von einem anderen Hermes geschrieben wurde als dem auf dem Laptop.

Prüf deine eigene Maschine

Schau nach, bevor du aufräumst — auf beiden Seiten:

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

Auf dem Host ist .curator_backups das verräterische Zeichen — wenn es existiert und du lokal nie ein Curator-Backup ausgeführt hast, ist es über die Leitung gekommen. .hub/lock.json ist mit bloßem Auge schwerer zu beurteilen; vergleiche die Änderungszeit mit deinem letzten Session-Teardown und mit der entfernten Kopie:

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

Die sauberste heute verfügbare Mitigation ist, die störenden Bäume auf dem Remote zu entfernen, damit es nichts Veraltetes zum Zurückkopieren gibt. Erst auflisten, dann nur löschen, was du als alte Duplikate erkennst — und zwar dann, wenn keine deiner Sessions auf dieser Maschine aktiv ist:

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'

Wenn du Curator-Backups auf dem Remote absichtlich behältst, behalte die neueste und beschneide den Rest, statt das Verzeichnis zu löschen.

Status

Das Ganze wird als Issue #139364 geführt (type/bug, backend/file-sync, P2), gemeldet am 15. Oktober 2026 gegen v0.21.7, mit einem eigenständigen Regressionstest im verlinkten Pull Request. Der vorgeschlagene Fix — PR #139365, keep skill bookkeeping trees out of sync-back — wendet dieselbe Ausschlussregel auf den Rückweg an. Zum Zeitpunkt dieses Textes ist der PR offen, nicht gemergt, das Verhalten steckt also weiterhin auf main.

Wenn du das SSH-Backend mit einem dauerhaften Remote benutzt, sind das dreißig Sekunden Prüfung wert, egal wann der Fix ausgeliefert wird: Die Bäume sollten dort nicht liegen, und jetzt weißt du, warum sie aufgetaucht sind.

Derselbe Remote-Backend hat noch andere Überraschungen produziert, die man daneben lesen sollte: den Fix für das Durchsickern von Umgebungsvariablen über SSH und die Anleitung zu OAuth über SSH.


Quellen: Issue #139364 und der verlinkte Fix-PR #139365 im Hermes-Agent-Repository; der Ausschluss auf der Upload-Seite ist _walk_skill_tree in tools/credential_files.py, der Rückweg sind _infer_host_path / _apply_staged_file in tools/environments/file_sync.py auf origin/main; die Liste der ausgeschlossenen Verzeichnisse ist EXCLUDED_SKILL_DIRS in agent/skill_utils.py.