
Seit einer Weile begrüßt mich mein Rechner zu jeder neuen Coding-Session mit einem kurzen Dhikr, einem Gedenken an Allah. Ein kleiner Anker, bevor die Technik losgeht. Technisch ist das ein PowerShell-Skript, das beim Sessionstart eine zufällige Zeile ausgibt:
$item = $dhikr | Get-Random
Write-Output "🤲 $item 🤲"
Irgendwann wurde aus dem Gruß das hier:
?? ????????? ??????? ?? SubhanAllah (Gepriesen sei Allah)
Die arabische Schrift war weg, ersetzt durch Fragezeichen. Das Skript war sauberes UTF-8, die Zeichen standen korrekt in der Datei. Trotzdem kam beim Start nur Buchstabensalat an. Genau bei dieser Sorte Fehler, alles sieht richtig aus und nichts stimmt, greife ich seit Kurzem zu einer Methode, die mir das Rätselraten abnimmt.
Die Methode: ein Rat aus sechs Gutachtern
Ich nenne sie den Weisenrat. Statt ein einzelnes KI-Modell zu fragen “was ist hier falsch”, setze ich sechs unabhängige Gutachter an dasselbe Problem, jeder mit einer eigenen Brille: ein Skeptiker, der jede Vermutung aktiv zu widerlegen versucht, ein Pedant für Details, ein Kreativer, der die Fragestellung selbst hinterfragt, ein Architekt für die Struktur, ein Pragmatiker für den Nutzen, ein Kostenwächter für den Aufwand. Keiner sieht die Befunde der anderen, bis alle fertig sind. Danach bewertet jeder die anonymisierten Befunde der anderen blind, dann fasst ein Vorsitzender zusammen. Der Trick ist nicht ein besseres Modell, sondern die Vielfalt der Blickwinkel. Plausible, aber falsche Erklärungen überleben das selten.
Genau das wurde hier zweimal wichtig.
Reproduzierbar machen
Zuerst den Fehler einklemmen. Wichtig ist, dass der Hook seinen stdout nicht in ein Terminal schreibt, sondern in eine Pipe. Das lässt sich mit Start-Process und echter Umleitung nachstellen:
$tmp = Join-Path $env:TEMP "dhikr.txt"
Start-Process "pwsh" -ArgumentList '-NoProfile','-File',"$HOME\.claude\dhikr.ps1" `
-RedirectStandardOutput $tmp -NoNewWindow -Wait
$bytes = [System.IO.File]::ReadAllBytes($tmp)
($bytes | Where-Object { $_ -eq 0x3F }).Count # 0x3F ist das Byte für '?'
Vor dem Fix steht hier eine zweistellige Zahl. Jedes arabische Zeichen ist im Ergebnis ein 0x3F geworden. Das ist die Signatur des Problems.
Ursache eins: das Output-Encoding
Bei umgeleitetem stdout fällt PowerShell unter Windows auf die Codepage des Hosts zurück, oft eine alte OEM-Tabelle wie CP850. Die kennt kein Arabisch. Der .NET-Encoder ersetzt darum jedes nicht abbildbare Zeichen durch 0x3F. Entscheidend: der Verlust passiert beim Schreiben in die Pipe, nicht erst bei der Anzeige. Wenn die Bytes einmal 3F 3F 3F sind, kann sie kein Leser mehr zurückrechnen.
Die Quelldatei ist dabei unschuldig. PowerShell 7 liest BOM-loses UTF-8 korrekt ein, der String ist im Speicher intakt. Es bricht erst auf dem Weg nach draußen. Der Fix ist eine Zeile, ganz oben im Skript, vor der ersten Ausgabe:
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
Bewusst UTF8Encoding($false), also ohne BOM. [System.Text.Encoding]::UTF8 würde eine Instanz mit BOM liefern und einen Drei-Byte-Vorspann EF BB BF in den Stream schreiben, der das Parsen der Ausgabe stört. Nach dem Fix liefert die Reproduktion von oben eine 0, kein einziges Fragezeichen-Byte mehr.
Zwei Sackgassen, die der Rat ausräumte
Spannend waren die naheliegenden Reparaturen, die nicht funktionieren. Beide hätte ich selbst probiert.
Ein BOM auf der Datei. Klingt logisch, hilft aber nicht. Ein BOM steuert, wie die Datei gelesen wird, nicht wie der stdout geschrieben wird. Getestet, mit BOM kommt weiterhin 0x3F an.
Der Umbau auf JSON. Ein verbreiteter Rat lautet, Hook-Ausgaben als JSON über ConvertTo-Json zu schreiben, dann sei das Encoding kein Thema mehr. Das stimmt für Windows PowerShell 5.1, aber nicht für PowerShell 7. Ein Gutachter hat es nicht behauptet, sondern auf der Zielmaschine gemessen:
$s = [char]0x0633 + [char]0x064F # zwei arabische Zeichen
@{ k = $s } | ConvertTo-Json -Compress
# PowerShell 7.6.2: {"k":"سُ"} <- rohe Zeichen, KEIN \uXXXX
# Windows PowerShell 5.1: {"k":"\uXXXX\uXXXX"} <- als \uXXXX escaped, ASCII-sicher
In PowerShell 7 stehen die arabischen Zeichen also roh im JSON und gehen durch denselben kaputten Encoder. Der JSON-Umbau verschiebt das Problem nur, er löst es nicht. Die Geschwister-Hooks, die JSON ausgeben, überlebten übrigens nur, weil ihr Text reiner ASCII war. Genau das ist der Wert der Methode: Vermutungen werden gemessen, nicht geglaubt.
Ursache zwei: der falsche Kanal
Nach dem Encoding-Fix kam der Dhikr trotzdem nicht. Das Skript lief sauber, ExitCode 0, korrekte UTF-8-Bytes auf stdout, leerer stderr, und ich sah weiterhin nichts. Hier lag die zweite, tiefere Ursache.
Das Skript hing als SessionStart-Hook an meinem KI-Werkzeug:
{
"matcher": "startup|clear",
"hooks": [
{ "type": "command", "command": "pwsh -NoProfile -File \"~/.claude/dhikr.ps1\"" }
]
}
Der stdout eines SessionStart-Hooks landet aber nicht sichtbar im Terminal. Er wird als Kontext an das Modell weitergereicht. Der Gruß war also da, nur an der falschen Adresse. Er erreichte die Maschine, nicht mich. Ob ich Write-Output oder Write-Host nutze, ändert daran nichts, beide enden im selben Kanal. Ein SessionStart-Hook ist schlicht der falsche Mechanismus für etwas, das ein Mensch sehen soll.
Update vom 10. Juni 2026: Diese Schlussfolgerung war zu schnell. Der stdout ist der falsche Kanal, der Hook selbst ist es nicht. Details im Update am Ende des Beitrags.
Die Lösung: der richtige Kanal
Wenn ich den Dhikr sehen will, gehört er nicht an den unsichtbaren Hook, sondern in das Profil meiner Konsole, das bei jedem Öffnen eines Terminals läuft und sichtbar druckt:
# $PROFILE.CurrentUserCurrentHost
if ($Host.Name -eq 'ConsoleHost') {
try { & "$HOME\.claude\dhikr.ps1" } catch { }
}
Zwei Schutzklauseln stecken drin. Der Guard auf ConsoleHost verhindert Ausgaben in nicht-interaktiven Kontexten und in Editor-Hosts. Das try/catch sorgt dafür, dass ein Skriptfehler niemals die ganze Shell blockiert, ein Profil, das wirft, vergiftet sonst jede Session. Der Profilpfad liegt unter Windows oft im OneDrive-Dokumenteordner, darum nie hartkodieren, sondern über $PROFILE.CurrentUserCurrentHost ermitteln. Verifiziert mit einer frischen Konsole:
🤲 حَسْبِيَ اللَّهُ لَا إِلَٰهَ إِلَّا هُوَ، عَلَيْهِ تَوَكَّلْتُ · Hasbiyallahu la ilaha illa huwa, alayhi tawakkaltu (Quran 9:129) 🤲
Eine Quelle für die Sammlung der Bittgebete, ein sauberes Encoding, der richtige Kanal. Seitdem grüßt mich beim Start wieder ein lesbares SubhanAllah.
Was ich mitnehme
Zwei Dinge. Erstens, der naheliegendste Fix ist nicht automatisch der richtige. Hier war er sogar zweimal falsch, BOM und JSON, und ohne die geprüfte Gegenrede hätte ich Stunden in Sackgassen verbracht. Zweitens, eine zweite, dritte, sechste Perspektive kostet wenig und findet, was ein einzelner Blick übersieht. Im Kleinen war es nur ein Gruß, der nicht ankam. Im Größeren ist es die Art, wie ich an knifflige Probleme herangehe, mit etwas mehr Demut gegenüber der eigenen ersten Vermutung.
Und der Gruß selbst passt ganz gut zur Lehre. Sabr, ein wenig Geduld, und das Vertrauen, dass die Lösung kommt, wenn man sauber hinschaut. Alhamdulillah.
Update: der Hook kann doch sprechen (10. Juni 2026)
Zwei Tage nach diesem Beitrag habe ich den Hook noch einmal reviewt, diesmal gegen die offizielle Dokumentation statt gegen Vermutungen. Ergebnis: es gibt einen sichtbaren Kanal, ich hatte ihn nur nicht gefunden. Ein Hook darf statt purem Text ein kleines JSON ausgeben, und das Feld systemMessage darin wird dem Menschen angezeigt, nicht dem Modell. Das gilt für jeden Hook-Typ.
Damit korrigiere ich die Aussage von oben: der SessionStart-Hook ist nicht der falsche Mechanismus, der nackte stdout ist nur der falsche Briefumschlag. Das Skript bedient jetzt beide Aufrufer:
param([switch]$Plain)
$item = $dhikr | Get-Random
if ($Plain) {
Write-Output "🤲 $item 🤲" # Terminal-Profil
} else {
@{ systemMessage = "🤲 $item 🤲" } | ConvertTo-Json -Compress # Hook, sichtbar
}
An der JSON-Sackgasse von oben ändert das nichts. JSON repariert kein Encoding, das tut weiterhin allein die OutputEncoding-Zeile. Aber sobald das Encoding stimmt, ist JSON mit systemMessage der richtige Umschlag für die Sichtbarkeit. Zwei verschiedene Probleme, zwei verschiedene Fixes.
Die Review-Runde fing nebenbei eine zweite Hook-Falle. Ein Nachbar-Hook gab bei jedem Stop der KI-Session eine Erinnerung als Kontext zurück. Klingt harmlos, erzeugt aber eine Endlosschleife: jede Erinnerung weckt die Session auf, jedes Aufwachen endet in einem neuen Stop, der Hook feuert erneut. Der Fix sind drei Bremsen. Das Flag stop_hook_active respektieren, das genau für diesen Fall existiert. Die Erinnerung auf einmal pro Session begrenzen. Und bei kaputtem Input lieber schweigen als feuern. Wer Hooks schreibt, die Kontext zurückgeben, sollte sich fragen, was beim übernächsten Aufruf passiert.
Der Gruß kommt jetzt auf beiden Wegen an, im Terminal über das Profil und in der KI-Session über den Hook, auch bei fortgesetzten Sessions. Alhamdulillah.
Update: das Modell spricht selbst (11. Juni 2026)
Einen Tag später die nächste Lektion. Der systemMessage-Umschlag funktioniert, aber nur im Terminal. Ich arbeite inzwischen oft im Desktop-Chat desselben Werkzeugs, und dort blieb der Gruß wieder aus. Diesmal ging die Diagnose schnell, die Methode kannte ich ja: erst beweisen, dass der Hook überhaupt feuert, dann dem Kanal misstrauen. Ein Nachbar-Hook legt bei jedem Stop eine Marker-Datei an. Die Marker waren da, die Anzeige nicht. Der Hook lief also, die Desktop-Oberfläche rendert systemMessage schlicht nicht.
Wenn die Oberfläche nichts anzeigen will, bleibt ein Kanal, der in jeder Oberfläche ankommt: die Antwort selbst. Ein UserPromptSubmit-Hook feuert bei jeder Eingabe und darf dem Modell Kontext mitgeben. Statt die UI zu bitten, etwas anzuzeigen, bitte ich jetzt das Modell, seine Antwort mit dem Dhikr abzuschließen:
[ordered]@{
hookSpecificOutput = [ordered]@{
hookEventName = 'UserPromptSubmit'
additionalContext = "Schliesse deine Antwort mit genau dieser Zeile ab: 🤲 $item 🤲"
}
} | ConvertTo-Json -Depth 3 -Compress
Ein Würfel-Gate im Skript drosselt das auf ein Viertel der Anfragen, ein Dauergruß würde sich abnutzen. Die ehrliche Schwäche dieser Lösung: sie hängt an der Anweisungstreue des Modells, nicht an einer Garantie der Oberfläche. Bei einer simplen Eine-Zeile-Anweisung ist die Quote in der Praxis sehr hoch, aber es bleibt ein Bitten, kein Erzwingen. Dafür ist es der einzige Kanal, der überall ankommt.
Damit grüßt der Dhikr jetzt auf drei Wegen, je nach Tür, durch die ich komme: in der PowerShell-Konsole über das Profil, im Terminal-CLI über systemMessage, im Desktop-Chat als Schlusszeile der Antwort. Und es hat etwas Passendes, dass die Maschine das Gedenken nicht mehr nur transportiert, sondern selbst ausspricht. Alhamdulillah.
