Command not found im Terminal des Agenten: Wie Hermes einen von Login-Shells zurückgesetzten PATH repariert


Du bittest den Agenten, in seinem Terminal hermes cron list auszuführen, damit er sieht, warum ein geplanter Job nie gefeuert hat. Zurück kommt eine einzige Zeile: bash: hermes: command not found. Du öffnest dein eigenes SSH-Fenster, tippst dasselbe, und es funktioniert. Zurück in der Session liefert auch command -v node nichts — obwohl du node mit nvm installiert hast.

Das ist kein Rechteproblem, und Hermes ist nicht kaputt. Irgendetwas hat PATH zurückgesetzt, still und vollständig.

Wer PATH zurückgesetzt hat

Hinter dem Terminal-Tool steckt eine bash. Damit die nvm-, asdf- und pyenv-Setups, auf die du dich verlässt, auch für den Agenten funktionieren, startet Hermes beim Anlegen der Session einmal eine Login-Shell und friert die daraus resultierende Umgebung ein — ~/.profile, ~/.bash_profile und ~/.bashrc zu sourcen ist genau der Weg, über den diese Version-Manager sich selbst in PATH eintragen. Dieser Teil ist Absicht und sinnvoll.

Das Problem ist /etc/profile unter Debian und Ubuntu: Es erweitert PATH nicht, es schreibt ihn auf eine feste Liste um (typischerweise /usr/local/bin:/usr/bin:/bin plus ein paar Spiele-Verzeichnisse). Sobald die Login-Shell fertig ist, zementiert der eingefrorene Snapshot diesen zurückgesetzten PATH. Alles, was Hermes beim Start hatte, ist weg: ~/.local/bin, /opt/hermes/bin, /opt/hermes/.venv/bin und jedes nvm-Shim-Verzeichnis.

Im Docker-Image ist das Symptom am deutlichsten: einmal mit bash -l in einen Container springen oder docker exec … bash -l ausführen, und ein blankes hermes endet mit Exit-Code 127. Hintergrundbefehle trifft es genauso, weil sie über bash -lic laufen.

Der Fix: wieder anhängen, was das Profil verworfen hat

Die Reparatur überschreibt nicht, was die Login-Shell entschieden hat. Sie bringt beides in Einklang:

  • nach dem Lauf des Login-Profils wird jedes Verzeichnis aus der Startumgebung, das jetzt fehlt, angehängt — in seiner ursprünglichen relativen Reihenfolge;
  • bereits vorhandene Verzeichnisse werden nie doppelt eingetragen;
  • nur absolute Einträge werden auf Existenz geprüft — ein relativer Eintrag wird gegen das Arbeitsverzeichnis der Shell aufgelöst, eine Prüfung aus Python heraus wäre also sinnlos;
  • fehlende Verzeichnisse werden übersprungen, statt als Ballast in PATH zu landen.

Das Anhängen am Ende ist die entscheidende Wahl. Was dein nvm- oder pyenv-Init entschieden hat, bleibt genau dort, wo es war: Ein Befehl, der vorher aufgelöst wurde, landet weiterhin auf derselben Binary; nur Befehle, die vorher gar keinen Treffer hatten, bekommen einen.

Hintergrundbefehle (spawn_local aus process_registry, bash -lic) stellen dasselbe Restore-Snippet voran, damit Vorder- und Hintergrund nicht uneins darüber sein können, welches python oder hermes existiert.

Das Docker-Image legt eine zweite Ebene darüber: /etc/profile.d/hermes.sh wird ins Image installiert, sodass jede Login-Shell die drei Image-Verzeichnisse wieder ergänzt — bereits gelistete bleiben, wo sie sind.

# /etc/profile.d/hermes.sh (im 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

Prüfen, was PATH wirklich ist

Drei Zeilen im Terminal des Agenten erzählen die ganze Geschichte:

echo $PATH                 # was der Agent tatsächlich bekommt
command -v hermes          # leer heißt: nicht gefunden
bash -lc 'echo $PATH'      # der zurückgesetzte PATH der Login-Shell, zum Vergleich

Tabelle von Symptom zu Ursache:

Symptom Echte Ursache Was tun
hermes: command not found in Docker Login-Shell hat PATH zurückgesetzt; dem Image fehlt der profile.d-Patch Image oder Hermes-Build aktualisieren
node / python fehlt, obwohl auf dem Host installiert /etc/profile hat das nvm/pyenv-Init aus ~/.profile überschrieben Im neuen Build automatisch behoben; in älteren Builds die Init-Dateien explizit auflisten
Nur Hintergrundbefehle scheitern Älterer Build hat das Restore-Snippet nicht mit dem Hintergrundpfad geteilt Aktualisieren, oder vorerst Vordergrundbefehle nutzen
Dein eigenes Skript findet ein Tool nicht Das Skript verließ sich auf ein Verzeichnis, das nur eine interaktive Shell hat Absolute Pfade nutzen oder die Datei in shell_init_files eintragen

Wenn du eigene Umgebungsdateien hast — etwa eine interne Toolchain —, sag Hermes genau, was gesourct werden soll, statt zu hoffen, dass es das Richtige rät:

terminal:
  auto_source_bashrc: true      # Standard; eine explizite shell_init_files-Liste übernimmt
  shell_init_files:
    - ~/.profile
    - ~/.config/hermes/env.sh

Grenzen, die man kennen sollte

  • Nur Verzeichnisse, die in der Umgebung vorhanden waren, mit der Hermes gestartet wurde, werden wiederhergestellt. Was dein Parent-Prozess nie hatte, wird nicht erfunden.
  • Wenn du ein Verzeichnis in deinem Profil bewusst aus PATH entfernt hast, es aber noch im Start-PATH stand, kommt es zurück. Das ist der beabsichtigte Kompromiss: funktionierende Befehle funktionierend halten.
  • Windows ist nicht betroffen — der native PATH wird mit Semikolon verbunden, und bash sieht eine konvertierte Kopie.
  • Reihenfolge ist Vorrang. Weil wiederhergestellte Einträge am Ende landen, kommen Systemverzeichnisse weiterhin vor deinen eigenen; PATH-Probleme also nicht dadurch „reparieren“, dass man ein beschreibbares Verzeichnis nach vorne stellt.

Für einen breiteren Umgebungscheck hermes doctor ausführen. Das Scratch-Verhalten im Terminal steht in temporäre Terminal-Verzeichnisse, Container-Layouts in shared container mode, und wie man die Shell komplett austauscht in Terminal-Backend-Plugins.

Release-Status

Der Fix ist am 29. Oktober 2026 in main gemerged (PR #146080). Das aktuelle Release v0.22.5 (23. Oktober 2026) enthält ihn nicht, ältere Builds können also weiterhin auf command not found laufen.

PATH-Bugs sind genau deshalb unangenehm, weil sie nie einen Fehler werfen — ein paar Verzeichnisse fehlen einfach. Immerhin kommen sie jetzt dorthin zurück, wo sie hingehören.