fix: UTF-8-Encoding für Claude CLI auf Windows erzwingen

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) <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.7 committed 2026-04-26 11:32:45 +02:00
1 parent b47ca33259
commit 32aad7feef
1 file changed
+79 -1
+79 -1
View File
@@ -39,7 +39,6 @@ public class ClaudeCliService
{ {
var psi = new ProcessStartInfo var psi = new ProcessStartInfo
{ {
FileName = _cliPath,
RedirectStandardInput = redirectInput, RedirectStandardInput = redirectInput,
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true, RedirectStandardError = true,
@@ -49,12 +48,91 @@ public class ClaudeCliService
StandardErrorEncoding = Encoding.UTF8 StandardErrorEncoding = Encoding.UTF8
}; };
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) foreach (var arg in args)
psi.ArgumentList.Add(arg); 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; return psi;
} }
/// <summary>
/// Quotet ein Argument im Stil von Microsoft CRT (CommandLineToArgvW),
/// passend für die Verwendung innerhalb von cmd.exe /C "...".
/// </summary>
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() private static string FindCliPath()
{ {
// 1. Expliziter Pfad über Umgebungsvariable (höchste Priorität) // 1. Expliziter Pfad über Umgebungsvariable (höchste Priorität)