From abe86644b93376d0981d40257d6798eaf3c912bc Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 30 Oct 2026 18:36:34 +0000 Subject: [PATCH 1/3] test(server): reproduce git hooks inheriting LC_ALL=C (#27749) A post-checkout hook that checks its locale sees the LC_ALL=C the driver forces on git, so createWorktree fails in the hook. Co-Authored-By: Claude Opus 5.5 --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index cd4b6949fad8..b8de83bf1d9a 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -3378,6 +3378,38 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { } }), ); + + // regression: #27749 hooks inherited the driver's forced LC_ALL=C + it.effect("runs post-checkout hooks under the server's own locale", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const { initialBranch } = yield* initRepoWithCommit(cwd); + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const driver = yield* GitVcsDriver.GitVcsDriver; + const serverLocale = process.env.LC_ALL ?? "unset"; + const seenLocale = path.join(yield* makeTmpDir("git-hook-locale-"), "lc-all"); + yield* writeTextFile( + cwd, + "hooks/post-checkout", + `#!/bin/sh\necho "\${LC_ALL-unset}" > '${seenLocale}'\n[ "\${LC_ALL-unset}" = '${serverLocale}' ]\n`, + ); + yield* fs.chmod(path.join(cwd, "hooks/post-checkout"), 0o755); + yield* git(cwd, ["config", "core.hooksPath", path.join(cwd, "hooks")]); + const worktreePath = path.join(yield* makeTmpDir("git-worktrees-"), "locale"); + + const result = yield* driver + .createWorktree({ cwd, path: worktreePath, refName: initialBranch, newRefName: "locale" }) + .pipe(Effect.result); + + assert.equal( + (yield* fs.readFileString(seenLocale)).trim(), + serverLocale, + "post-checkout hook should see the server's LC_ALL", + ); + assert.isTrue(Result.isSuccess(result), "worktree creation failed in the hook"); + }), + ); it.effect.each(["commit", "short-commit", "tag", "revision"] as const)( "creates a worktree from a %s without recording it as a PR base", (kind) => From 44aed7045c715d8ef6526b2e320afa7048d200be Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 30 Oct 2026 18:42:54 +0000 Subject: [PATCH 2/3] fix(server): git hooks keep the user's locale Git commands forced LC_ALL=C so failure classification could match English stderr. Hooks inherit that env, so post-checkout and post-merge hooks that need a UTF-8 LC_CTYPE (Ansible, uv) failed and worktree creation reported `git worktree add failed`. Set LANGUAGE=C instead: gettext still returns untranslated messages, and the user's LC_* stay intact. Applies to stable-diagnostics git calls, submodule updates, and clones. Fixes pingdotgg/t3code#27749 Co-Authored-By: Claude Opus 5.5 --- .../src/sourceControl/SourceControlRepositoryService.ts | 3 ++- apps/server/src/vcs/GitVcsDriverCore.test.ts | 4 ++-- apps/server/src/vcs/GitVcsDriverCore.ts | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/server/src/sourceControl/SourceControlRepositoryService.ts b/apps/server/src/sourceControl/SourceControlRepositoryService.ts index 44d31a43b697..1e9c48893dad 100644 --- a/apps/server/src/sourceControl/SourceControlRepositoryService.ts +++ b/apps/server/src/sourceControl/SourceControlRepositoryService.ts @@ -79,7 +79,8 @@ const CLONE_ENV = { // Force piped progress immediately; refuse credential prompts that would hang without a tty. GIT_PROGRESS_DELAY: "0", GIT_TERMINAL_PROMPT: "0", - LC_ALL: "C", + // English progress without overriding LC_CTYPE, which a global post-checkout hook inherits. + LANGUAGE: "C", } satisfies NodeJS.ProcessEnv; function mapRepositoryError(operation: string, provider: SourceControlProviderKind) { diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index b8de83bf1d9a..c2046ab5fc3c 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -857,7 +857,7 @@ it.effect.each([ return yield* Effect.die("expected Git command"); if (command.args[0] !== "fetch") return makeNonRepositoryHandle(); assert.deepEqual(command.args, ["fetch", "--quiet", "--", "origin"]); - assert.equal(command.options.env?.LC_ALL, "C"); + assert.equal(command.options.env?.LANGUAGE, "C"); assert.equal(command.options.env?.GIT_TERMINAL_PROMPT, "0"); yield* Ref.update(attempts, (count) => count + 1); return ChildProcessSpawner.makeHandle({ @@ -1202,7 +1202,7 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { command.args[0] === "branch" || command.args[0] === "checkout") ) { - locales.push(command.options.env?.LC_ALL); + locales.push(command.options.env?.LANGUAGE); } return delegate.spawn(command); }); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index c5bdf3d068ef..3776c515522c 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1183,7 +1183,8 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ...options, env: { ...options.env, - LC_ALL: "C", + // English diagnostics without overriding LC_CTYPE, which hooks inherit. + LANGUAGE: "C", }, }); @@ -4155,7 +4156,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* : ["submodule", "update", "--init"], onSubmoduleLine ? { - env: { LC_ALL: "C" }, + env: { LANGUAGE: "C" }, progress: { onStdoutLine: onSubmoduleLine, onStderrLine: onSubmoduleLine }, } : {}, From 595f92e328c0ac8cf9edfe457a60cf0e72b6678e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 30 Oct 2026 18:51:32 +0000 Subject: [PATCH 3/3] fix(server): triage source checkout keeps the user's locale The triage source clone runs git checkout, which runs a global post-checkout hook under the same forced LC_ALL=C. Use LANGUAGE=C there too. Co-Authored-By: Claude Opus 5.5 --- apps/server/src/triage/TriageService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/triage/TriageService.ts b/apps/server/src/triage/TriageService.ts index f7a93f24a664..14d12c0af9e1 100644 --- a/apps/server/src/triage/TriageService.ts +++ b/apps/server/src/triage/TriageService.ts @@ -23,7 +23,8 @@ const T3CODE_REPOSITORY_URL = "https://github.com/pingdotgg/t3code"; const GIT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "", - LC_ALL: "C", + // English messages without overriding LC_CTYPE, which a global post-checkout hook inherits. + LANGUAGE: "C", } satisfies NodeJS.ProcessEnv; /** Starts in-app triage sessions: the same files `t3 triage` writes, plus a source clone. */