Annulé après 11 heures : pourquoi les prompts d'approbation d'Hermes gardent une échéance


Si vous avez lu hier que les prompts d’approbation d’Hermes vous attendaient désormais indéfiniment, vous avez peut-être remarqué qu’ils se sont remis à décompter et à refuser une fois le temps écoulé. Cela ressemble à un mainteneur qui change d’avis, mais c’est en réalité une ligne de conception tracée rapidement : le changement résolvait une vraie gêne et créait un blocage là où personne ne peut répondre. Il a été annulé 11 heures et 32 minutes après sa fusion.

Cet article ne prend pas parti. Il expose la chronologie, les raisons et — surtout — le comportement qui s’applique réellement aujourd’hui, parce que c’est lui qui détermine comment vous devez configurer approvals.

Le comportement aujourd’hui

Commençons par figer la conclusion, car elle détermine si vous devez toucher à votre configuration. Au 3 octobre 2026 :

Surface Durée de vie du prompt
CLI interactive, TUI, application Desktop Décompte jusqu’à approvals.timeout (300 s par défaut), puis le traite comme non approuvé et bloque la commande
Telegram / Discord / Slack et autres plateformes de messagerie Utilise aussi approvals.timeout ; une notification push peut rester invisible pendant des minutes
Questions de clarification (agent.clarify_timeout) 3600 s (1 heure) par défaut ; à l’expiration, l’attente se débloque avec “user did not respond within Xm”

Le contrat central est inchangé et il compte : un délai expiré n’est pas une approbation. Ce n’est pas “personne n’a répondu, donc allez-y” — c’est “personne n’a répondu, donc ne l’exécutez pas”. La copie amont pour un prompt annulé le dit en une ligne : le silence ne vaut pas consentement. Une échéance est la valeur par défaut sûre, et le prix à payer est que vous pouvez perdre une opération en silence.

Ce que faisait le changement annulé

Le 1er octobre à 16h50 CST, la PR #130167 a été fusionnée. Son principe était de séparer les surfaces selon qu’un humain se trouve ou non devant l’écran :

Les prompts d’approbation et de clarification dans la CLI, le TUI et l’application Desktop n’expireraient plus. Ils resteraient affichés jusqu’à ce qu’on y réponde, que le tour soit interrompu ou que la session se ferme. Les plateformes de messagerie, les éditeurs ACP et les plugins de transport d’approbation conservaient approvals.timeout et agent.clarify_timeout, parce qu’une notification push peut réellement rester invisible et que les boutons des plateformes expirent.

Mécaniquement, c’était une paire de ContextVar par tour — set_prompts_wait_for_answer() / reset_prompts_wait_for_answer() — plus un résolveur unique, approval_wait_seconds(), qui renvoyait le maximum sûr pour la plateforme quand l’interrupteur était lié, et approvals.timeout sinon. Le prompt de la CLI, la boucle de polling d’approbation du gateway TUI/Desktop et le plafond d’attente humaine lisaient tous ce point d’entrée unique.

Comme instinct produit, c’est séduisant : vous vous levez pour chercher de l’eau, vous revenez, le prompt est toujours là, vous ne perdez pas l’opération.

Pourquoi il a été annulé cette nuit-là

Le 2 octobre à 04h23 CST, la PR #130826 a annulé l’ensemble. Le corps de l’annulation donne trois raisons, dont deux sont des blocages durs.

Premièrement, la direction avait déjà été rejetée avant la fusion. La première ligne de l’annulation est sans détour : “Les prompts d’attente infinie ont été rejetés comme direction avant la fusion de #130167.” C’est un échec de processus, pas un échec technique.

Deuxièmement, cela cassait hermes chat -q. C’est celle qui comptait vraiment. Le mode requête unique est non surveillé : personne n’est assis là pour répondre. Retirer l’échéance d’un prompt d’approbation signifie que la session attend indéfiniment une réponse qui ne viendra jamais — elle se bloquait simplement. L’amont a dû livrer un patch de suivi (82c63c505a9) pour rendre son échéance à chat -q, et cette annulation l’emporte avec elle, car corriger le blocage était sa seule raison d’exister.

Troisièmement, un test de bout en bout s’est mis à échouer à chaque exécution. tests/e2e/core/providers/test_native_codex_app_server_faults.py échouait à chaque exécution e2e sur main jusqu’à ce que ce même patch de suivi corrige la seule surface qu’il touchait.

L’ampleur du retour en arrière : 11 commits plus le patch de suivi dépendant, couvrant 57 fichiers, chacun identique au byte près à son état antérieur à #130167 (5 fichiers portent des commits ultérieurs sans rapport qui restent intacts). Ce qui est revenu, c’est le comportement que vous voyez aujourd’hui — les panneaux d’approbation de la CLI et du TUI/Desktop décomptent et refusent au délai configuré, les demandes de clarification portent à nouveau leur échéance, un tour de groupe en Bot Mode bloqué sur un utilisateur garde son plafond dur de 3 heures, et la documentation, le texte des réglages et les chaînes de localisation ont été restaurés.

Un détail vaut la peine d’être conservé : le changement n’a jamais été livré dans une version. Le dernier tag à l’époque était v2026.9.24 (version v0.21.5, publiée le 24 septembre) et #130167 a été fusionnée le 1er octobre. Seules les installations qui suivent main ont vu le prompt d’attente infinie ; les utilisateurs d’une version stable ne l’ont jamais vu.

Ce que ces 11 heures enseignent réellement

Ôtez la mécanique et il reste trois règles utiles.

Une échéance est un mécanisme de sécurité, pas une verrue d’UX. Le décompte porte une responsabilité : quand personne ne confirme, le système doit pouvoir s’arrêter tout seul. Retirez-le et “non surveillé” devient “bloqué pour toujours”, ce qui est pire qu’un refus — un refus pousse l’agent à trouver une autre voie, alors qu’un blocage s’accumule. Si les prompts vous ennuient, les bons réglages sont la durée du délai et le mode d’approbation, pas l’absence de délai.

“Quelqu’un peut-il répondre ?” relève de l’intention, pas de la durée d’attente. Une session -q, une tâche cron et un déclencheur webhook sont tous fondamentalement différents de vous assis devant un terminal. Hermes donne à chacun un interrupteur explicite : approvals.single_query_mode, approvals.cron_mode et approvals.unattended_mode, chacun deny ou approve, chacun par défaut à deny — parce que sur ces surfaces, bloquer immédiatement et laisser l’agent contourner le blocage vaut mieux que d’attendre tout un délai et d’échouer en fermant.

N’essayez pas de résoudre le problème en augmentant le délai. Le plafond dans le code n’est pas décoratif : approvals.timeout est borné au maximum sûr pour la plateforme (agent.deadline.MAX_SAFE_TIMEOUT_S, 365 jours hors Windows) parce qu’une valeur plus grande fait déborder time_t à l’intérieur de Thread.join / Lock.acquire et faisait planter tous les lots d’outils parallèles sur macOS lors des tests en conditions réelles. Le texte d’avertissement est aussi formaté pour les humains — 300 s s’affiche comme “5 minutes”, 90 s comme “90 seconds” — afin que la CLI, le résultat de l’outil et la carte du gateway disent tous la même chose.

Que faire à la place

Lisez les valeurs actuelles plutôt que de deviner :

hermes config get approvals.timeout
hermes config get approvals.mode

Si 300 s est trop court pour vos plateformes de messagerie, augmentez-le — ne le désactivez pas :

hermes config set approvals.timeout 1800

Routez les surfaces non surveillées selon l’intention, afin qu’elles décident immédiatement au lieu d’attendre une fenêtre que personne ne remplira :

hermes config set approvals.single_query_mode approve   # vous faites confiance à vos exécutions -q
hermes config set approvals.unattended_mode deny        # les webhooks / l'API doivent bloquer

Bloquez les commandes qui ne devraient jamais s’exécuter, avec une liste de refus — c’est une liste de globs fnmatch insensible à la casse, et une correspondance est bloquée même en mode yolo :

approvals:
  deny:
    - "rm -rf *"
    - "DROP DATABASE *"

Pour la sémantique complète et un moyen de tester vos règles à blanc, consultez notre guide des approbations non surveillées et le tutoriel de test à blanc des approbations. Si vous utilisez le mode smart par défaut, approvals.smart_policy ajoute vos propres règles au prompt système du relecteur.

À retenir

En une ligne : cette annulation n’est pas une régression — c’est un problème correct confié au mauvais mécanisme. Un délai d’approbation est une soupape de sécurité. Si les prompts vous dérangent, ajustez la durée et le mode ; ne retirez pas la soupape.

Une correction de notre part : l’article d’hier, les prompts d’approbation qui attendent une réponse, décrivait le comportement de #130167 à l’époque. Nous y avons ajouté une note de mise à jour. Que l’amont change de direction en une journée est le résultat que cet article aurait dû anticiper et n’a pas anticipé — c’est exactement pourquoi nous essayons de ne pas traiter “tout juste fusionné” comme “décision finale”.