Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/integrations/terminal/ExecaTerminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment on lines +52 to +60
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "Windows-specific" but these environment variables are set unconditionally on all platforms. In contrast, Terminal.ts correctly guards these with process.platform === "win32". This creates inconsistent behavior: Execa terminals will set these vars on macOS/Linux while VSCode terminals will not. Consider either adding a platform check here to match Terminal.ts, or updating the comment to explain why these are intentionally set on all platforms (e.g., for consistency with the existing LANG/LC_ALL unconditional override).

Fix it with Roo Code or mention @roomote and request a fix.

},
})`${command}`

Expand Down
12 changes: 12 additions & 0 deletions src/integrations/terminal/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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", () => {
Expand Down
65 changes: 65 additions & 0 deletions src/integrations/terminal/__tests__/Terminal.getEnv.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
})
})
})
Loading