hermes update se bloque, prétend être à jour ou refuse votre CA — quatre correctifs ont été fusionnés

C’est vendredi soir et vous tapez hermes update avant de fermer votre session. L’écran reste bloqué sur Fetching... et ne bouge plus pendant dix minutes. Vous faites Ctrl+C, vous essayez sur une autre machine, et cette fois il répond instantanément : Already up to date! — sauf qu’après le redémarrage, hermes --version affiche toujours l’ancienne version. La pire variante est la troisième : là, le code a bel et bien bougé, rien ne signale d’erreur, et vous vous croyez à jour.
Ces trois expériences viennent de quatre bugs distincts, tous fusionnés sur main le 2026-09-28. Aucun n’affecte vos conversations quotidiennes, mais chacun casse précisément l’opération vers laquelle vous vous tournez quand vous en avez le plus besoin. Voici ce qui les déclenche, quelle était la cause racine, et ce que la mise à jour fait désormais.
1. Clones superficiels : la mise à jour se bloque dans le fetch jusqu’au timeout
L’installateur clone avec --depth 1 pour gagner du temps — uniquement l’historique du commit le plus récent. Ça va très bien jusqu’à ce que le checkout prenne suffisamment de retard.
Depuis un clone depth-1, un simple git fetch origin main pousse le serveur à envoyer l’ascendance complète de chaque branche latérale fusionnée au-delà de la frontière superficielle. Ce téléchargement ne peut pas se terminer dans la limite réseau de 300 secondes. Et l’étape d’unshallow qui aurait réglé le problème était planifiée après une mise à jour réussie — elle ne s’exécutait donc jamais : timeout, mise à jour échouée, pas d’unshallow, même timeout la fois suivante.
Désormais, l’ordre est inversé : avant le fetch plafonné, heal_shallow_history complète le graphe de commits (commits uniquement, un budget de 900 secondes, et il tire la branche cible avec lui), si bien que le fetch limité qui suit reste réellement petit.
Le même lot corrige deux défaillances plus subtiles :
- Crash de
index-pack --promisoravec git 2.53+. Sur certains objets locaux,pack-objectstombe sur unBUG()interne. Le dégât vient de ce que l’état persiste dans le dépôt : tous les fetch suivants — la vérification de mise à jour comme la mise à jour elle-même — meurent dessus. Le correctif est volontairement étroit : sur exactement cette signature de crash, une seule nouvelle tentative avec-c remote.origin.promisor=, par invocation, sans toucher à la config du dépôt. --filter=tree:0transformait silencieusement les clones complets en clones partiels. La mise à jour passait ce filtre à chaque fetch de tags, etgit fetch --filterécritremote.origin.promisoretpartialclonefiltercomme effet de bord — un clone complet sain était donc converti en clone partiel, et chaque fetch suivant héritait d’une dépendance promisor. Les fetch de tags réutilisent maintenant le filtre du clone lui-même : un clone partiel répète son filtre, un clone complet fetch sans filtre.
Deux commandes vous disent si vous êtes sur ce genre de machine :
git rev-parse --is-shallow-repository # true signifie superficiel
git config --get remote.origin.partialclonefilter
2. Il annonçait « Already up to date! » alors que le code venait de changer
Celui-ci est plus vicieux : vous croyez qu’il ne s’est rien passé, alors que le pull a bien eu lieu et que la synchronisation des dépendances, la migration de config et le redémarrage du gateway ont tous été sautés.
Le déclencheur est un checkout détaché — par exemple parce que vous avez fait un rollback plus tôt avec git checkout <tag>. Dans cet état, la mise à jour basculait HEAD sur main puis comptait HEAD..origin/main. Votre main local pointait déjà sur origin/main, donc le compte était nul : elle affichait Already up to date! et sortait, alors même que le code réellement en cours d’exécution venait d’être remplacé. Le compte part désormais du commit qui tournait vraiment.
Le chemin de rollback était erroné de la même façon. Si la mise à jour se terminait sur une erreur de syntaxe, elle lançait git reset --hard sur la branche de mise à jour : un utilisateur en checkout détaché se retrouvait garé sur main, et les commits d’une branche de feature garée étaient écrits sur main. Le comportement se scinde désormais en deux cas :
- checkout détaché :
git checkout --detach <sha>, retour au commit depuis lequel la mise à jour a démarré ; - branche garée :
git checkout <branch>, sans toucher àmain. Si un autre worktree occupe cette branche, son commit est restauré en détaché à la place.
Une règle couvre les deux cas : une mise à jour échouée ne laisse jamais votre installation sur du code cassé.
3. Derrière un réseau d’entreprise, SSL_CERT_FILE n’atteint jamais git
Si votre CA racine est distribuée via la variable d’environnement SSL_CERT_FILE, vous obtenez une double personnalité : la vérification de version (Python) réussit, et git fetch échoue en TLS à chaque fois. La raison est banale — le libcurl de git ne lit pas SSL_CERT_FILE. Il ne reconnaît que GIT_SSL_CAINFO ou un http.sslCAInfo configuré.
Le correctif recopie SSL_CERT_FILE dans GIT_SSL_CAINFO une fois par mise à jour ou par vérification, dans l’environnement du processus plutôt que dans les arguments d’une seule commande — les fetch promisor paresseux d’un clone partiel bénéficient ainsi eux aussi de la CA. Si vous définissez GIT_SSL_CAINFO explicitement, ou si vous avez http.sslCAInfo dans votre config git, c’est votre réglage qui l’emporte.
Le même lot permet à hermes pm install de fonctionner derrière un miroir npm interne, ce qui échouait auparavant quand l’URL publique épinglée dans le lock file était injoignable.
Vos deux vérifications, de votre côté :
echo "$SSL_CERT_FILE"
git config --get http.sslCAInfo
4. Des échecs comptabilisés comme des succès
Les trois premiers relèvent du « le travail n’a pas été fait ». Celui-ci relève du « ce n’est pas fait et on vous a dit que si », et c’est précisément ce qui vous fait perdre une soirée.
Une écriture refusée dans .env affichait quand même ✓ Set. Quand hermes config set enregistre les identifiants d’un provider, il passe par le chemin d’écriture du .env, et ce chemin, en détectant un conflit de verrou, se contentait d’afficher une ligne et de rendre la main. L’appelant ne vérifiait jamais le résultat : il continuait, effaçait les valeurs dupliquées de config.yaml, affichait ✓ Set et sortait avec le code 0 — vous croyiez l’identifiant enregistré. Désormais, les quatre writers multi-store appellent require_env_writable avant tout effet de bord : le CLI sort avec le code 1, PUT/DELETE /api/env répond 400 avec la raison, et le model.disconnect du TUI vérifie chaque variable en amont.
Un dashboard qui ne revenait pas était comptabilisé comme restarted. Pendant une mise à jour, Hermes prend la main et redémarre un dashboard que vous aviez lancé à la main. Il rejouait l’argv /proc du processus arrêté mot pour mot — y compris le chemin d’un launcher que la reprise avait déjà réécrit en shim shell — si bien que l’enfant mourait immédiatement sur un SyntaxError. Popen réussissait, lui, donc la mise à jour affichait quand même ✓ restarted. L’argv est maintenant reconstruit sur le launcher lié à l’installation qu’utilisent tous les autres spawns Hermes, un enfant qui sort en moins d’une seconde compte comme un redémarrage échoué, et un dashboard qui ne revient jamais fait basculer tout le reçu de mise à jour en partial avec le code de sortie 1.
Regardez avant de sauter : deux commandes en lecture seule
hermes update --check # une version plus récente existe-t-elle ? (ne change rien)
hermes update --plan # plan en lecture seule : type d'installation, tous les processus Hermes à travers les profiles, comment chacun va redémarrer
--plan est le compagnon naturel de ces correctifs : il affiche ce que la mise à jour va toucher et ne change rien, il est donc sans risque sur un hôte multi-profile en production. Les autres flags à connaître sont --no-gateway-restart (changer le code sans redémarrer le gateway), --keep-stash, et --backup / --no-backup.
Ce que vous devez faire
Rien. Les quatre correctifs sont sur main ; votre prochaine hermes update les récupère. Si votre machine est un clone superficiel ou se trouve derrière une CA d’entreprise, résistez à l’envie de faire Ctrl+C cette fois-ci — laissez-lui le temps de compléter l’historique. Ensuite, hermes doctor est un bon moyen de confirmer que dépendances, version de config et processus en cours sont tous alignés.
Si le déroulé de la mise à niveau reste flou, commencez par notre guide pas à pas de la mise à niveau, et par conserver les skills et la config de votre profile à travers une mise à jour. À savoir : c’est cette même mise à jour qui achève la couche de compatibilité des plugins — si votre plugin importe encore un ancien chemin, vous le saurez juste après. Vous préférez partir de zéro ? Le guide d’installation couvre toutes les plateformes.