« command not found » dans le terminal de l'agent : comment Hermes corrige un PATH réinitialisé par les login shells

Vous demandez à l’agent de lancer hermes cron list dans son terminal pour voir pourquoi un job planifié n’a jamais démarré. Il répond en une ligne : bash: hermes: command not found. Vous ouvrez votre propre fenêtre SSH, vous tapez la même chose, et ça marche. De retour dans la session, command -v node ne renvoie rien non plus — alors que vous avez installé node avec nvm.
Ce n’est pas un problème de permissions, et Hermes n’est pas cassé. Quelque chose a réinitialisé PATH, discrètement et complètement.
Qui a réinitialisé PATH
Derrière l’outil terminal, il y a un bash. Pour que les setups nvm, asdf et pyenv dont vous dépendez fonctionnent aussi pour l’agent, Hermes démarre un login shell une seule fois, à la création de la session, et capture l’environnement obtenu — sourcer ~/.profile, ~/.bash_profile et ~/.bashrc est précisément la manière dont ces gestionnaires de versions s’ajoutent au PATH. Cette partie est intentionnelle et utile.
Le problème vient du /etc/profile de Debian et Ubuntu : il n’étend pas PATH, il le réécrit avec une liste fixe (typiquement /usr/local/bin:/usr/bin:/bin, plus quelques répertoires de jeux). Une fois le login shell terminé, l’instantané capturé fige ce PATH réinitialisé. Tout ce que Hermes avait au démarrage a disparu : ~/.local/bin, /opt/hermes/bin, /opt/hermes/.venv/bin, et tous les répertoires de shims nvm.
Dans l’image Docker, le symptôme est le plus flagrant : entrez dans un conteneur avec bash -l, ou docker exec … bash -l, et un simple hermes ressort en 127. Les commandes d’arrière-plan sont touchées de la même manière, puisqu’elles passent par bash -lic.
Le correctif : rajouter ce que le profil a laissé tomber
La réparation n’écrase pas ce que le login shell a décidé. Elle réconcilie les deux :
- après l’exécution du profil de login, tout répertoire de l’environnement de lancement désormais absent est rajouté, dans son ordre relatif d’origine ;
- les répertoires déjà présents ne sont jamais dupliqués ;
- seules les entrées absolues sont testées pour existence — une entrée relative se résout par rapport au répertoire de travail du shell, la vérifier depuis Python n’aurait aucun sens ;
- les répertoires manquants sont ignorés plutôt que collés dans PATH comme du poids mort.
Rajouter à la fin est le choix déterminant. Ce que votre init nvm ou pyenv a décidé reste exactement là où il était, donc une commande qui se résolvait avant pointe toujours vers le même binaire ; seules les commandes qui n’avaient aucune correspondance en gagnent une.
Les commandes d’arrière-plan (spawn_local de process_registry, bash -lic) préfixent le même fragment de restauration, si bien que premier plan et arrière-plan ne peuvent pas diverger sur le python ou le hermes qui existe.
L’image Docker ajoute une seconde couche : /etc/profile.d/hermes.sh est installé dans l’image, donc chaque login shell rajoute les trois répertoires de l’image — en laissant en place ceux déjà listés.
# /etc/profile.d/hermes.sh (à l'intérieur de l'image)
for __hermes_p in /opt/data/.local/bin /opt/hermes/.venv/bin /opt/hermes/bin; do
case ":$PATH:" in
*":$__hermes_p:"*) ;;
*) PATH="$__hermes_p:$PATH" ;;
esac
done
unset __hermes_p
export PATH
Vérifier ce qu’est vraiment PATH
Trois lignes dans le terminal de l’agent racontent toute l’histoire :
echo $PATH # ce que l'agent reçoit réellement
command -v hermes # vide signifie introuvable
bash -lc 'echo $PATH' # le PATH réinitialisé du login shell, pour comparaison
Tableau symptôme → cause :
| Symptôme | Cause réelle | Que faire |
|---|---|---|
hermes: command not found dans Docker |
Le login shell a réinitialisé PATH ; l’image n’a pas le patch profile.d | Mettre à jour l’image ou le build Hermes |
node / python introuvables alors qu’ils sont installés sur l’hôte |
/etc/profile a écrasé l’init nvm/pyenv de ~/.profile |
Corrigé automatiquement dans le nouveau build ; sur les builds plus anciens, listez explicitement les fichiers d’init |
| Seules les commandes d’arrière-plan échouent | L’ancien build ne partageait pas le fragment de restauration avec le chemin d’arrière-plan | Mettre à jour, ou utiliser des commandes au premier plan en attendant |
| Votre propre script ne trouve pas un outil | Le script comptait sur un répertoire que seul un shell interactif possède | Utilisez des chemins absolus, ou ajoutez le fichier à shell_init_files |
Si vous avez des fichiers d’environnement personnalisés — une toolchain interne, par exemple — dites à Hermes exactement quoi sourcer au lieu d’espérer qu’il devine :
terminal:
auto_source_bashrc: true # par défaut ; une liste shell_init_files explicite prend le dessus
shell_init_files:
- ~/.profile
- ~/.config/hermes/env.sh
Des limites qu’il vaut mieux connaître
- Seuls les répertoires présents dans l’environnement avec lequel Hermes a été lancé sont restaurés. Ce que votre processus parent n’a jamais eu n’est pas inventé.
- Si vous avez volontairement retiré un répertoire de PATH dans votre profil alors qu’il figurait encore dans le PATH de lancement, il revient. C’est le compromis assumé : garder opérationnelles les commandes qui fonctionnaient.
- Windows n’est pas concerné — le PATH natif est joint par des points-virgules et bash en voit une copie convertie.
- L’ordre, c’est la priorité. Comme les entrées restaurées arrivent à la fin, les répertoires système passent toujours avant les vôtres ; ne « corrigez » pas un problème de PATH en plaçant un répertoire inscriptible en tête.
Pour un contrôle d’environnement plus large, lancez hermes doctor. Le comportement de l’espace de travail du terminal est traité dans les répertoires temporaires du terminal, les dispositions de conteneur dans le mode conteneur partagé, et le remplacement complet du shell dans les plugins de backend terminal.
Statut de la release
Le correctif a été mergé dans main le 29 octobre 2026 (PR #146080). La release courante, v0.22.5 (23 octobre 2026), ne l’inclut pas, donc les builds plus anciens peuvent toujours tomber sur command not found.
Les bugs de PATH sont désagréables précisément parce qu’ils ne lèvent jamais d’erreur — quelques répertoires disparaissent, c’est tout. Au moins reviennent-ils maintenant là où ils doivent être.