diff --git a/src/ClaudeDo.Data/Git/GitService.cs b/src/ClaudeDo.Data/Git/GitService.cs index b2c00879..613724d4 100644 --- a/src/ClaudeDo.Data/Git/GitService.cs +++ b/src/ClaudeDo.Data/Git/GitService.cs @@ -704,6 +704,11 @@ public sealed class GitService // (mktree "input format error") and would prepend to stdin commit messages. StandardInputEncoding = stdinData is not null ? new UTF8Encoding(false) : null, }; + // Force English stderr/stdout so the English-only substring matches below + // (e.g. "ambiguous argument 'HEAD'", "tell me who you are") work regardless + // of the OS locale git for Windows picks up. + psi.Environment["LC_ALL"] = "C"; + psi.Environment["LANGUAGE"] = "C"; psi.ArgumentList.Add("-C"); psi.ArgumentList.Add(workDir); foreach (var a in args) psi.ArgumentList.Add(a); diff --git a/tests/ClaudeDo.Worker.Tests/Runner/GitServiceBootstrapTests.cs b/tests/ClaudeDo.Worker.Tests/Runner/GitServiceBootstrapTests.cs index 14924db9..4192da5e 100644 --- a/tests/ClaudeDo.Worker.Tests/Runner/GitServiceBootstrapTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Runner/GitServiceBootstrapTests.cs @@ -122,6 +122,61 @@ public class GitServiceBootstrapTests : IDisposable Assert.Equal("ClaudeDo ", author); } + [Fact] + public async Task RevParseHead_EmptyRepo_FriendlyMessage_EvenUnderNonEnglishLocale() + { + if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; } + + var dir = InitEmptyRepo(); + var previousLanguage = Environment.GetEnvironmentVariable("LANGUAGE"); + var previousLcAll = Environment.GetEnvironmentVariable("LC_ALL"); + // Simulate a non-English OS/git locale on the *ambient* process environment. + // GitService must override it per-call so its English-only stderr matches + // (e.g. "ambiguous argument 'HEAD'") keep working regardless. + Environment.SetEnvironmentVariable("LANGUAGE", "de"); + Environment.SetEnvironmentVariable("LC_ALL", "de_DE.UTF-8"); + try + { + var ex = await Assert.ThrowsAsync(() => _git.RevParseHeadAsync(dir)); + Assert.Contains("no commits yet", ex.Message); + } + finally + { + Environment.SetEnvironmentVariable("LANGUAGE", previousLanguage); + Environment.SetEnvironmentVariable("LC_ALL", previousLcAll); + } + } + + [Fact] + public async Task CommitAsync_MissingIdentity_FallsBackToClaudeDoIdentity_EvenUnderNonEnglishLocale() + { + if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; } + + var dir = InitEmptyRepo(); + GitRepoFixture.RunGit(dir, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "--allow-empty", "-m", "seed"); + GitRepoFixture.RunGit(dir, "config", "user.name", ""); + GitRepoFixture.RunGit(dir, "config", "user.email", ""); + File.WriteAllText(Path.Combine(dir, "work.txt"), "content\n"); + await _git.AddAllAsync(dir); + + var previousLanguage = Environment.GetEnvironmentVariable("LANGUAGE"); + var previousLcAll = Environment.GetEnvironmentVariable("LC_ALL"); + Environment.SetEnvironmentVariable("LANGUAGE", "de"); + Environment.SetEnvironmentVariable("LC_ALL", "de_DE.UTF-8"); + try + { + await _git.CommitAsync(dir, "feat(test): identity fallback under non-English locale"); + + var author = GitRepoFixture.RunGit(dir, "log", "-1", "--format=%an <%ae>").Trim(); + Assert.Equal("ClaudeDo ", author); + } + finally + { + Environment.SetEnvironmentVariable("LANGUAGE", previousLanguage); + Environment.SetEnvironmentVariable("LC_ALL", previousLcAll); + } + } + public void Dispose() { foreach (var dir in _tempDirs)