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 @@ -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}`

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 @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
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()
})
})
})
16 changes: 16 additions & 0 deletions src/integrations/terminal/__tests__/TerminalRegistry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}))
Expand Down Expand Up @@ -49,6 +61,7 @@ describe("TerminalRegistry", () => {
ROO_ACTIVE: "true",
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
...WIN32_UTF8_ENV,
},
})
})
Expand All @@ -71,6 +84,7 @@ describe("TerminalRegistry", () => {
PROMPT_COMMAND: "sleep 0.05",
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
...WIN32_UTF8_ENV,
},
})
} finally {
Expand All @@ -94,6 +108,7 @@ describe("TerminalRegistry", () => {
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
ITERM_SHELL_INTEGRATION_INSTALLED: "Yes",
...WIN32_UTF8_ENV,
},
})
} finally {
Expand All @@ -116,6 +131,7 @@ describe("TerminalRegistry", () => {
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
POWERLEVEL9K_TERM_SHELL_INTEGRATION: "true",
...WIN32_UTF8_ENV,
},
})
} finally {
Expand Down
Loading