Quitter une session a supprimé du code que vous n'avez jamais commité

Vous faites tourner trois sessions en parallèle. L’une d’elles est à mi-parcours de l’édition d’un fichier — pas encore de commit, vous finirez demain. Vous quittez la session et fermez le terminal. Le lendemain matin, le répertoire sous .worktrees/ a disparu, et la branche aussi.
Vous n’avez pas mal retenu son emplacement. Le nettoyage à la sortie pour worktree: true oublie une vérification : il regarde les commits, jamais les fichiers.
Ce que fait le mode worktree
Démarrer une session avec hermes -w (ou définir worktree: true dans la config) donne à chaque session CLI son propre worktree sous .worktrees/, sur sa propre branche :
worktree: false # par défaut : uniquement quand -w est passé
worktree_sync: true # par défaut : branche depuis le tip distant fraîchement récupéré ; false utilise le HEAD local
C’est ainsi que plusieurs agents éditent, commitent, poussent et ouvrent des PR sans se marcher dessus. Brancher depuis le tip distant frais est délibéré aussi — la PR finit par être limitée à votre changement au lieu d’hériter de tout le retard du clone local.
Le problème se situe à l’étape de sortie.
Ce que la doc promet vs. ce que le code fait
La documentation de configuration, dans la section sur l’isolation par git worktree, est explicite :
Les worktrees propres sont supprimés à la sortie ; les dirty trees sont conservés pour récupération manuelle.
Pendant ce temps, _cleanup_worktree dans cli.py énonce son propre critère dans sa docstring :
def _cleanup_worktree(info: Dict[str, str] = None) -> None:
"""Remove a worktree and its branch on exit; kept only when it has unpushed commits."""
Seuls les commits non poussés sont conservés. Le helper qui décide, _worktree_has_unpushed_commits(), exécute :
git log --oneline HEAD --not --remotes
Autrement dit, il demande quels commits sur HEAD aucun remote ne possède. Le statut de l’arbre de travail n’est jamais lu. Donc :
| Ce que le worktree contient à la sortie | Résultat réel |
|---|---|
| Un nouveau fichier non suivi, jamais commité | worktree SUPPRIMÉ |
| Un fichier suivi modifié, non commité | worktree SUPPRIMÉ |
| Un commit pas encore poussé | worktree conservé |
L’étape finale est git worktree remove <path> --force plus git branch -D <branch>. --force signifie exactement ce que cela sous-entend : ignorer les changements non commités dans l’arbre de travail.
Pourquoi il existe deux critères différents
Hermes a en fait deux chemins qui gèrent les worktrees, et ils ne sont pas d’accord.
L’un est le nettoyage à la sortie dans cli.py, décrit plus haut — commits uniquement.
L’autre est le pruner au démarrage dans hermes_cli/worktree_ops.py, et lui comprend « dirty » : _worktree_is_dirty() lance git status --porcelain (fichiers non suivis inclus), échoue en sécurité vers True, et son verdict est enregistré comme dirty, qui est un membre de _PRESERVE_REASONS. Le commentaire au-dessus est d’une franchise inhabituelle :
Garde-fous à tous les tiers et âges : les dirty trees ne sont jamais supprimés.
Donc ce n’est pas « Hermes ne sait pas distinguer dirty de clean ». Ce sont deux chemins avec deux critères différents : le pruner de fond protège les fichiers dirty, le nettoyage à la sortie protège les commits. Ce sont les utilisateurs qui tombent sur le chemin de sortie.
Comment vous protéger
Tant que ce critère n’est pas aligné, voici ce qui fonctionne aujourd’hui.
- Commitez avant de quitter, même en WIP. Un commit non poussé fait survivre l’arbre :
git add -A && git commit -m "wip: still working" - Ou stashez, fichiers non suivis inclus. Le
-udegit stash -uest la partie importante — sans lui, les nouveaux fichiers n’existent encore qu’à l’intérieur du worktree. - Ne considérez pas « la session est encore ouverte » comme une sécurité pour vos fichiers. Le travail non commité n’est sûr que tant que la session vit ; dès que vous quittez, il est à portée de
--force. - Jetez un œil au statut quand vous faites tourner des sessions en parallèle.
git -C .worktrees/<name> status --porcelain; une sortie non vide signifie que cet arbre sera supprimé à la sortie. - Faites commiter et pousser la session. Les sessions worktree sont conçues pour laisser l’agent commiter, pousser et ouvrir des PR ; une fois poussé, le critère « commits non poussés » conserve l’arbre, et vous gagnez un second filet de sécurité.
- Auditez régulièrement. Cela vaut la peine de savoir ce que le check-up worktree introduit dans la v0.20.5 couvre et ne couvre pas.
Si c’est déjà arrivé : commencez par git worktree list et git branch pour voir si la branche a survécu. S’il y avait ne serait-ce qu’un commit avant la sortie, ce contenu est encore dans le stockage d’objets et récupérable depuis la branche ou le reflog. Mais des fichiers non commités et non suivis supprimés par --force n’ont aucun chemin de récupération au niveau git — que vous les retrouviez dépend de buffers d’éditeur, de snapshots système ou de sauvegardes, pas de git. Mieux vaut être franc là-dessus que laisser entendre un sauvetage qui n’existe pas.
Où en est la situation
L’issue #129482 a été ouverte le 2026-09-30, étiquetée P1, contre le code à 41755854, et elle était encore ouverte au moment de la rédaction. Le correctif naturel serait que le nettoyage à la sortie appelle lui aussi _worktree_is_dirty(), pour que les deux chemins partagent un seul critère. En attendant, faites les étapes 1 et 2 ci-dessus.
Ce n’est pas non plus le premier de ces bugs « ça a l’air propre, ça a discrètement perdu quelque chose ». Le garde-fou de rollback pour dépôts imbriqués couvre les rollbacks touchant des répertoires qui n’ont jamais été capturés, et les frontières de session expliquent ce que les sessions partagent réellement. Les deux valent la peine d’être intégrés à vos habitudes.
À retenir
La leçon n’est pas « n’utilisez pas les worktrees ». C’est qu’une opération de suppression devrait utiliser le critère le plus conservateur disponible : pour décider si un arbre peut être supprimé, ce qui se trouve dans le répertoire de travail compte bien plus que ce qui se trouve dans le graphe de commits. Un seul fichier non commité à moitié fini vaut généralement plus que dix commits poussés.
Tant que le code n’est pas d’accord, l’auto-défense tient en une phrase : commitez ou stashez avant de quitter.