From 32aad7feefa17f7875aa7c54dad046266efeb728 Mon Sep 17 00:00:00 2001 From: Meleki Date: Sun, 26 Apr 2026 11:32:45 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20UTF-8-Encoding=20f=C3=BCr=20Claude=20CLI?= =?UTF-8?q?=20auf=20Windows=20erzwingen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auf Windows kam es sporadisch zu Replacement-Chars (�) für Umlaute und Em-Dash, weil claude.exe Output mit CP1252/CP850 schreiben konnte. Lösung: CLI über cmd.exe wrappen und Codepage 65001 (UTF-8) erzwingen, zusätzlich LANG/LC_ALL/PYTHONIOENCODING auf UTF-8 setzen. Co-Authored-By: Claude Opus 4.7 (1M context) --- Services/ClaudeCliService.cs | 84 ++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/Services/ClaudeCliService.cs b/Services/ClaudeCliService.cs index c553c19..99e80f2 100644 --- a/Services/ClaudeCliService.cs +++ b/Services/ClaudeCliService.cs @@ -39,7 +39,6 @@ public class ClaudeCliService { var psi = new ProcessStartInfo { - FileName = _cliPath, RedirectStandardInput = redirectInput, RedirectStandardOutput = true, RedirectStandardError = true, @@ -49,12 +48,91 @@ public class ClaudeCliService StandardErrorEncoding = Encoding.UTF8 }; - foreach (var arg in args) - psi.ArgumentList.Add(arg); + if (OperatingSystem.IsWindows()) + { + // Auf Windows: über cmd.exe wrappen und Codepage 65001 (UTF-8) erzwingen. + // Sonst kann der Child-Prozess (claude.exe) Output mit CP1252/CP850 schreiben, + // was beim UTF-8-Decode zu Replacement-Chars (�) für Umlaute / Em-Dash führt. + var sb = new StringBuilder(); + sb.Append("/S /C \"chcp 65001 >nul && "); + sb.Append(QuoteArgForCmd(_cliPath)); + foreach (var a in args) + { + sb.Append(' '); + sb.Append(QuoteArgForCmd(a)); + } + sb.Append('"'); + + psi.FileName = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; + psi.Arguments = sb.ToString(); + } + else + { + psi.FileName = _cliPath; + foreach (var arg in args) + psi.ArgumentList.Add(arg); + } + + // UTF-8-Locale für den Child setzen (Node.js / diverse Tools respektieren das). + // Schadet auf Windows nicht, hilft auf manchen Linux-Setups (z.B. systemd-Diensten ohne LANG). + psi.Environment["LANG"] = "en_US.UTF-8"; + psi.Environment["LC_ALL"] = "en_US.UTF-8"; + psi.Environment["PYTHONIOENCODING"] = "utf-8"; return psi; } + /// + /// Quotet ein Argument im Stil von Microsoft CRT (CommandLineToArgvW), + /// passend für die Verwendung innerhalb von cmd.exe /C "...". + /// + private static string QuoteArgForCmd(string arg) + { + if (string.IsNullOrEmpty(arg)) + return "\"\""; + + bool needsQuoting = false; + foreach (var c in arg) + { + if (c == ' ' || c == '\t' || c == '"' || c == '\n' || c == '\v') + { + needsQuoting = true; + break; + } + } + + if (!needsQuoting) + return arg; + + var sb = new StringBuilder(); + sb.Append('"'); + int backslashes = 0; + foreach (var c in arg) + { + if (c == '\\') + { + backslashes++; + } + else if (c == '"') + { + // 2n+1 Backslashes vor einem Anführungszeichen + sb.Append('\\', backslashes * 2 + 1); + sb.Append('"'); + backslashes = 0; + } + else + { + sb.Append('\\', backslashes); + sb.Append(c); + backslashes = 0; + } + } + // 2n Backslashes am Ende (vor dem schließenden ") + sb.Append('\\', backslashes * 2); + sb.Append('"'); + return sb.ToString(); + } + private static string FindCliPath() { // 1. Expliziter Pfad über Umgebungsvariable (höchste Priorität)