fix: add Windows UTF-8 encoding environment variables for terminals

This fixes character corruption in terminal output when Windows systems use
non-UTF-8 encodings like GBK (code page 936).

Adds the following environment variables on Windows:
- PYTHONIOENCODING=utf-8: Force UTF-8 for Python stdin/stdout/stderr
- PYTHONUTF8=1: Enable Python 3.7+ UTF-8 mode
- RUBYOPT=-EUTF-8: Force Ruby to use UTF-8 encoding

Fixes #10709
This commit is contained in:
Roo Code 2026-01-17 04:02:46 +00:00
parent 87a5afa629
commit 958d941e55
4 changed files with 115 additions and 0 deletions

View file

@ -49,6 +49,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

@ -159,6 +159,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

@ -72,6 +72,22 @@ describe("ExecaTerminalProcess", () => {
)
})
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 preserve existing environment variables", async () => {
process.env.EXISTING_VAR = "existing"
terminalProcess = new ExecaTerminalProcess(mockTerminal)
@ -91,6 +107,19 @@ describe("ExecaTerminalProcess", () => {
expect(calledOptions.env.LANG).toBe("en_US.UTF-8")
expect(calledOptions.env.LC_ALL).toBe("en_US.UTF-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")
})
})
describe("basic functionality", () => {

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()
})
})
})