fix: add Windows UTF-8 encoding env vars for terminals with fixed tests

Adds Windows-specific UTF-8 environment variables (PYTHONIOENCODING,
PYTHONUTF8, RUBYOPT) to both VSCode terminals and Execa terminals to
prevent character corruption when the system uses non-UTF-8 encodings
like GBK (code page 936).

Fixes the test failures from PR #10796 by updating TerminalRegistry
tests to conditionally include the Windows-specific env vars based on
the platform.

Closes #10709
This commit is contained in:
Roo Code 2026-03-06 09:59:09 +00:00
parent 0892455db2
commit fbe6bd5329
5 changed files with 131 additions and 0 deletions

View file

@ -50,6 +50,15 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
// Ensure UTF-8 encoding for Ruby, CocoaPods, etc.
LANG: "en_US.UTF-8",
LC_ALL: "en_US.UTF-8",
// Windows-specific UTF-8 environment variables to prevent character corruption
// when the system uses non-UTF-8 encodings like GBK (code page 936)
// See: https://github.com/RooCodeInc/Roo-Code/issues/10709
// Python: Force UTF-8 encoding for stdin/stdout/stderr
PYTHONIOENCODING: "utf-8",
// Python 3.7+: Enable UTF-8 mode
PYTHONUTF8: "1",
// Ruby: Force UTF-8 encoding
RUBYOPT: "-EUTF-8",
},
})`${command}`

View file

@ -160,6 +160,18 @@ export class Terminal extends BaseTerminal {
VTE_VERSION: "0",
}
// Add Windows-specific UTF-8 environment variables to prevent character corruption
// when the system uses non-UTF-8 encodings like GBK (code page 936)
// See: https://github.com/RooCodeInc/Roo-Code/issues/10709
if (process.platform === "win32") {
// Python: Force UTF-8 encoding for stdin/stdout/stderr
env.PYTHONIOENCODING = "utf-8"
// Python 3.7+: Enable UTF-8 mode
env.PYTHONUTF8 = "1"
// Ruby: Force UTF-8 encoding
env.RUBYOPT = "-EUTF-8"
}
// Set Oh My Zsh shell integration if enabled
if (Terminal.getTerminalZshOhMy()) {
env.ITERM_SHELL_INTEGRATION_INSTALLED = "Yes"

View file

@ -94,6 +94,35 @@ describe("ExecaTerminalProcess", () => {
expect(calledOptions.env.LC_ALL).toBe("en_US.UTF-8")
})
it("should set Windows-specific UTF-8 environment variables", async () => {
await terminalProcess.run("echo test")
const execaMock = vitest.mocked(execa)
expect(execaMock).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
// Python UTF-8 encoding
PYTHONIOENCODING: "utf-8",
PYTHONUTF8: "1",
// Ruby UTF-8 encoding
RUBYOPT: "-EUTF-8",
}),
}),
)
})
it("should override existing Python and Ruby encoding environment variables", async () => {
process.env.PYTHONIOENCODING = "latin-1"
process.env.PYTHONUTF8 = "0"
process.env.RUBYOPT = "-ELATIN-1"
terminalProcess = new ExecaTerminalProcess(mockTerminal)
await terminalProcess.run("echo test")
const execaMock = vitest.mocked(execa)
const calledOptions = execaMock.mock.calls[0][0] as any
expect(calledOptions.env.PYTHONIOENCODING).toBe("utf-8")
expect(calledOptions.env.PYTHONUTF8).toBe("1")
expect(calledOptions.env.RUBYOPT).toBe("-EUTF-8")
})
it("should use execaShellPath when set", async () => {
BaseTerminal.setExecaShellPath("/bin/bash")
await terminalProcess.run("echo test")

View file

@ -0,0 +1,65 @@
// npx vitest run integrations/terminal/__tests__/Terminal.getEnv.spec.ts
import { Terminal } from "../Terminal"
describe("Terminal.getEnv", () => {
let originalPlatform: PropertyDescriptor | undefined
beforeAll(() => {
originalPlatform = Object.getOwnPropertyDescriptor(process, "platform")
})
afterAll(() => {
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform)
}
})
describe("common environment variables", () => {
it("should set VTE_VERSION to 0", () => {
const env = Terminal.getEnv()
expect(env.VTE_VERSION).toBe("0")
})
it("should set PAGER to empty string on Windows", () => {
Object.defineProperty(process, "platform", { value: "win32" })
const env = Terminal.getEnv()
expect(env.PAGER).toBe("")
})
it("should set PAGER to cat on non-Windows", () => {
Object.defineProperty(process, "platform", { value: "linux" })
const env = Terminal.getEnv()
expect(env.PAGER).toBe("cat")
})
})
describe("Windows UTF-8 encoding fix", () => {
beforeEach(() => {
Object.defineProperty(process, "platform", { value: "win32" })
})
it("should set PYTHONIOENCODING to utf-8 on Windows", () => {
const env = Terminal.getEnv()
expect(env.PYTHONIOENCODING).toBe("utf-8")
})
it("should set PYTHONUTF8 to 1 on Windows", () => {
const env = Terminal.getEnv()
expect(env.PYTHONUTF8).toBe("1")
})
it("should set RUBYOPT to -EUTF-8 on Windows", () => {
const env = Terminal.getEnv()
expect(env.RUBYOPT).toBe("-EUTF-8")
})
it("should not set Python/Ruby UTF-8 vars on non-Windows", () => {
Object.defineProperty(process, "platform", { value: "linux" })
const env = Terminal.getEnv()
expect(env.PYTHONIOENCODING).toBeUndefined()
expect(env.PYTHONUTF8).toBeUndefined()
expect(env.RUBYOPT).toBeUndefined()
})
})
})

View file

@ -6,6 +6,18 @@ import { TerminalRegistry } from "../TerminalRegistry"
const PAGER = process.platform === "win32" ? "" : "cat"
// Windows-specific UTF-8 environment variables added to prevent character corruption
// when the system uses non-UTF-8 encodings like GBK (code page 936)
// See: https://github.com/RooCodeInc/Roo-Code/issues/10709
const WIN32_UTF8_ENV =
process.platform === "win32"
? {
PYTHONIOENCODING: "utf-8",
PYTHONUTF8: "1",
RUBYOPT: "-EUTF-8",
}
: {}
vi.mock("execa", () => ({
execa: vi.fn(),
}))
@ -49,6 +61,7 @@ describe("TerminalRegistry", () => {
ROO_ACTIVE: "true",
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
...WIN32_UTF8_ENV,
},
})
})
@ -71,6 +84,7 @@ describe("TerminalRegistry", () => {
PROMPT_COMMAND: "sleep 0.05",
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
...WIN32_UTF8_ENV,
},
})
} finally {
@ -94,6 +108,7 @@ describe("TerminalRegistry", () => {
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
ITERM_SHELL_INTEGRATION_INSTALLED: "Yes",
...WIN32_UTF8_ENV,
},
})
} finally {
@ -116,6 +131,7 @@ describe("TerminalRegistry", () => {
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
POWERLEVEL9K_TERM_SHELL_INTEGRATION: "true",
...WIN32_UTF8_ENV,
},
})
} finally {