Zum Inhalt springen
Zurück

Dhikr als Fragezeichen: PowerShell, UTF-8 und sechs Gutachter

Aus einer Reihe Fragezeichen formt sich ein Band aus warmem Licht

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 fuer '?'

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.

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.

Papiercollage in der aroui-Farbwelt, aus einer Reihe Fragezeichen formt sich ein Band aus warmem Licht, daneben ein alter Computer, eine Moschee, eine Orange, ein Olivenzweig, Weizenähren, der Hamburger Hafen und ein Burger als Wortspiel


Beitrag teilen:

Nächster Beitrag
Medical-IT, warum Arztpraxen andere Regeln brauchen