"No such tool available" + Session-Neustart-Vorschlag trotz aktivem Toolset
(Verbindungstest komplett gruen). Ursache: Claude Code CLI versucht die im
System-Prompt nur als TEXT beschriebenen Tools (spotify_*, ...) ueber sein
eigenes natives Funktionsaufruf-/ToolSearch-System zu finden/aufzurufen,
weil das "# Verfuegbare Tools"-Listenformat wie eine echte Tool-Registrierung
aussieht. Da nichts davon ueber die Anthropic-API als tools-Parameter
registriert ist (nur Prompt-Text), schlaegt das immer fehl -> Claude
interpretiert den Fehler faelschlich als "nicht verbunden/nicht geladen".
Fix: expliziter Warnabsatz im Tool-Block, der genau das verbietet und klar
macht dass "No such tool available" ein Bedienfehler (falsches Aufrufmuster)
ist, kein Verbindungsproblem. Analog zu ARIAs eigenem System-Prompt, der
diese Klarstellung schon enthaelt.
Live-Test (spotify_test_connection.py) bestaetigte: Login, Toolset und
API-Erreichbarkeit sind alle OK. Trotzdem antwortete Claude im Chat
'Tools in Werkzeugliste beschrieben, aber nicht verbunden/aktiv' - eine
frei erfundene Einschraenkung, die nirgends im injizierten System-Prompt
stand (der beschreibt nur Namen/Schema der Tools, nichts zum Verbindungs-
status). Klassisches Modell-Hedging bei Unsicherheit ueber Backend-Status.
Ergaenzt im Tool-Block: expliziter Hinweis dass alle gelisteten Tools
bereits authentifiziert/einsatzbereit sind und ohne Rueckfrage zur
Verbindung genutzt werden sollen, Fehler nur anhand echter Tool-Antworten
(z.B. 401/403) melden.
Prueft direkt in Hermes' eigenem Code (get_auth_status, _get_platform_tools,
SpotifyClient), ob Toolset aktiviert, Login vorhanden und ein echter
GET /me/player/devices erfolgreich ist -- ohne Umweg ueber Claude/Proxy,
da LLM-Textantworten wie 'nicht verbunden/aktiv' kein verlaesslicher
Diagnosewert sind.
Root Cause fuer "Claude kennt Hermes-Tools nicht" gefunden: unser
openai-to-cli.js baut systemPrompt korrekt, manager.js liest options.systemPrompt
korrekt fuer --system-prompt -- aber der stock npm-Code in server/routes.js
kopiert beim subprocess.start(cliInput.prompt, {model, sessionId}) nur model+
sessionId, nie systemPrompt. Also war options.systemPrompt immer undefined,
--system-prompt kam nie mit echtem Inhalt an, Claude lief die ganze Zeit mit
seiner eigenen Default-Identitaet statt der injizierten Hermes-Tools.
Neuer sed patcht jetzt auch routes.js und reicht systemPrompt durch (trifft
beide Call-Sites: Streaming + Non-Streaming). Verifiziert lokal gegen das
echte npm-Paket claude-max-api-proxy@1.0.0.
Verifiziert (lokal): claude --system-prompt "" faellt komplett auf die eigene
Default-Identitaet zurueck (eigene native Tools, eigene Account-Connectors) statt
mit leerem Prompt zu laufen. Das erklaert exakt das beobachtete Symptom bei Hermes
(Claude nennt Gmail/Calendar/Drive + Cron/Task, kennt aber keinerlei Spotify-Tool).
Hypothese: extractSystemPrompt(request.messages, request.tools) liefert fuer
Hermes' Requests einen leeren/zu duennen String -- entweder weil Hermes gar kein
tools-Feld mitschickt (Spotify-Toolset in hermes tools nie aktiviert?) oder weil
keine system-Message im erwarteten Format ankommt. Logging zeigt messageRoles,
toolsCount, toolNames und systemPromptLen/-Preview in docker logs hermes-proxy --
damit klaeren wir das anhand echter Daten statt weiter zu raten.
hermes-proxy laeuft mit echter Claude-Code-CLI-Session auf ggf. demselben
Anthropic-Account wie ARIA -> account-weite Connectors (Gmail/Calendar/Drive)
und Claude Codes eigenes Skill-System wurden automatisch mitgeladen und
haben mit den per Prompt injizierten <tool_call>-Tools (Hermes-Plugins wie
Spotify) konkurriert. --safe-mode deaktiviert alle nativen Customizations
fuer diese Session, sodass nur noch die injizierten externen Tools sichtbar
sind.
Root Cause im echten Hermes-Sourcecode (hermes_cli/web_server.py,
_ws_host_origin_reason) verifiziert, nicht geraten: WebSocket-Upgrades
(/api/ws, /api/pub, /api/events) haben einen zweiten Guard neben dem
Host-Header, der zusaetzlich den Origin-Header gegen den Bind (127.0.0.1)
prueft. Browser schickt die echte Server-Adresse als Origin -> origin_mismatch
-> WS wird mit 4403 (Browser: 1006) geschlossen, komplett ohne Logging
(anders als /api/console + /api/pty). Fix: Origin-Header in Caddy entfernen,
dann ueberspringt Hermes den Check ganz.
hermes-agent-dashboard als separater Container ist raus. Stattdessen
supervised s6 Gateway und Dashboard jetzt als Geschwister-Prozesse im
selben hermes-agent-Container (HERMES_DASHBOARD=1 + HERMES_DASHBOARD_HOST=
127.0.0.1) -- laut Startup-Log selbst "the recommended setup for the s6
container image". Das ist der wahrscheinliche Fix fuer zwei offene Bugs:
Dashboard zeigte Gateway-Status immer als "Stopped" (lokale Prozess-
Inspektion sah den Gateway-Prozess im getrennten Container nie) und der
Restart-Button warf "no such gateway 'default'" (loeste s6-Services nur im
eigenen Container auf). README + Architektur-Diagramm entsprechend
aktualisiert.
Hermes' Dashboard-Backend prueft den eingehenden Host-Header gegen den
Host, an den es gebunden ist -- Caddy reichte bisher den originalen
Host-Header vom Browser (Server-IP/Domain) durch, was Hermes als
'Invalid Host header' ablehnte. header_up ueberschreibt den Host jetzt
explizit. Plus Troubleshooting-Eintrag in der README.
Hermes' eigenes Auth-Gate (dashboard.basic_auth/HERMES_DASHBOARD_PASSWORD_HASH)
engagiert sich laut eigener Fehlermeldung ohnehin nur bei einem 0.0.0.0-Bind,
der Dashboard-Container bindet aber fest auf 127.0.0.1 -- das Gate haette hier
nie gegriffen. Statt es als "Fallback falls Bind mal wieder wandert" zu
behalten, jetzt sauber raus: HERMES_DASHBOARD_PASSWORD_HASH aus .env.example
und docker-compose.yml, der auskommentierte dashboard:-Block aus
config.yaml.example, README-Setup auf 8 statt 9 Schritte umnummeriert.
Caddy (TLS + eigene Basic-Auth) ist damit die einzige noetige Auth-Schicht --
ein Layer statt zwei, weniger Setup, weniger Verwirrung. Stale Kommentare im
Caddy-Compose-Block (redeten noch von "zweiter Basic-Auth-Schicht noetig weil
unklar ob dashboard.basic_auth greift") ebenfalls korrigiert.
Dashboard bindet seit dem Caddy-Umbau nur noch auf 127.0.0.1 -- Hermes'
Auth-Gate engagiert sich laut eigener Fehlermeldung NUR bei einem
0.0.0.0-Bind, greift also gar nicht mehr. HERMES_DASHBOARD_PASSWORD_HASH
ist damit optional statt Pflicht (kein docker-compose-Abbruch mehr bei
leerem Wert), der dashboard: basic_auth-Block in config.yaml.example ist
standardmaessig auskommentiert. Caddy (TLS + eigene Basic-Auth) bleibt
die einzige im Normalbetrieb noetige Auth-Schicht. README/.env.example
entsprechend entschlackt -- Hash-Erzeugen + Dollarzeichen-Escaping nur
noch als Fallback dokumentiert, falls der Bind mal wieder auf 0.0.0.0
wandert.
Entrypoint im caddy-Service prueft beim Start ob /certs/dashboard.{crt,key}
schon existiert und erzeugt es sonst selbst (apk add openssl + req -x509,
100 Jahre). Persistent im Volume, kein manueller Schritt mehr noetig.
Manuelle Erzeugung bleibt als Fallback in der README dokumentiert (z.B.
fuer eigenes /CN).
Stefans Entscheidung: 0.0.0.0-Bind mit unverschluesseltem Basic-Auth war nur
Testzustand. Jetzt bindet hermes-agent-dashboard wieder auf 127.0.0.1, ein
neuer Caddy-Service (network_mode: host) terminiert TLS auf 443 mit einem
manuell erzeugten selbstsignierten Zertifikat (100 Jahre Laufzeit, kein
Let's-Encrypt-Domain-Zwang) und erzwingt eine zweite, eigene Basic-Auth-Schicht
(bcrypt-Hash in CADDY_BASIC_AUTH_HASH, getrennt von Hermes' pbkdf2-Hash) -
noetig weil Hermes' eigenes Auth-Gate laut Fehlermeldung nur bei 0.0.0.0-Binds
"engagiert". README + .env.example um die Setup-Schritte (Cert-Erzeugung,
Caddy-Hash, $-Escaping-Falle) ergaenzt.
docker-compose interpretiert rohe $-Zeichen im pbkdf2-Hash (.env) selbst
als Variablen-Referenzen und ersetzt sie stillschweigend durch Leerstring
(sichtbar als "WARN variable is not set"). Ergebnis: verstümmelter Hash,
Dashboard bleibt trotz "richtig" eingetragener Werte bei "no auth
providers are registered" haengen. Fix: jedes $ im Hash durch $$ ersetzen.
README dokumentierte den seit 19.07.2026 pflichtigen Dashboard-Auth-Block
(HERMES_DASHBOARD_USER/PASSWORD_HASH, dashboard.basic_auth in config.yaml)
noch gar nicht - neuer Setup-Schritt 4, Schritt 7/8 umnummeriert, Architektur-
Diagramm und Sicherheitshinweis auf den 0.0.0.0-Dashboard-Stand gebracht.
Nebenbei echten Bug gefunden: .env.example und config.yaml.example sagten,
den Passwort-Hash per 'docker exec hermes-agent-dashboard' zu erzeugen -
aber genau dieser Container startet ohne bereits gesetztes
HERMES_DASHBOARD_PASSWORD_HASH gar nicht (docker-compose :?-Pflichtvar).
Hash muss gegen 'hermes-agent' erzeugt werden, der teilt sich das Image
ohne die Pflicht-Var.
Hermes' eigenes Auth-Gate blockt den Container-Start hart, wenn --host 0.0.0.0
gesetzt ist aber keine Auth konfiguriert wurde. Basic-Auth-Block in
config.yaml.example ergaenzt, dashboard-Service bekommt HERMES_DASHBOARD_USER
+ HERMES_DASHBOARD_PASSWORD_HASH als Pflicht-Env, .env.example dokumentiert
die Hash-Erzeugung ueber Hermes' eingebautes plugins.dashboard_auth.basic.
Hatte nur image: hermes-agent ohne eigenen build:-Kontext, im Unterschied
zum hermes-agent-Service. Compose versucht bei up erst zu pullen (Image
existiert nicht auf Docker Hub), hermes-agent faengt das per build:-Fallback
ab, dashboard nicht -> harter Error. Fix: gleicher build-Kontext + explizites
pull_policy: build fuer beide, damit gar nicht erst gepullt wird.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Stefan-Entscheidung: Hermes Agent (Nous Research) laeuft auf derselben
Maschine wie der Claude-Max-Proxy, nicht mehr remote. Ergaenzt:
- hermes-agent + hermes-agent-dashboard Services (offizielle Definition,
gebaut aus separat geklontem hermes-agent-src/), network_mode: host
- hermes-agent-config/config.yaml.example: seedet Hermes' model:-Block
automatisch auf provider: custom -> unser hermes-gateway, per ${VAR}-
Expansion aus .env (kein manuelles Config-Editieren mehr)
- hermes-gateway bindet jetzt nur noch auf 127.0.0.1 statt 0.0.0.0, da
Hermes lokal mitlaeuft und kein Remote-Zugriff mehr noetig ist
- .gitignore fuer hermes-agent-src/, hermes-data/, .env (Secrets/Sourcecode
gehoeren nicht ins Repo)
- README auf Single-Machine-Architektur aktualisiert, inkl. Setup-Schritten
und Einordnung des optionalen API_SERVER (fuer spaeteren mobilen Client,
andere Richtung als unser Proxy)