From 0284a3c224ea7712a5845d1f43a80a2747b30664 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 7 Dec 2025 05:12:21 +0000 Subject: [PATCH 1/2] fix: handle PowerShell ENOENT error in os-name on Windows - Add try-catch block around osName() call to handle PowerShell not being available - Provide fallback using os.platform() and os.release() when osName() fails - Add comprehensive tests for both success and error scenarios - Fixes #9859 where users without PowerShell in PATH get API request failures --- .../sections/__tests__/system-info.spec.ts | 86 +++++++++++++++++++ src/core/prompts/sections/system-info.ts | 13 ++- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/core/prompts/sections/__tests__/system-info.spec.ts diff --git a/src/core/prompts/sections/__tests__/system-info.spec.ts b/src/core/prompts/sections/__tests__/system-info.spec.ts new file mode 100644 index 00000000000..8588fcb386f --- /dev/null +++ b/src/core/prompts/sections/__tests__/system-info.spec.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import os from "os" + +// Mock the modules - must be hoisted before imports +vi.mock("os-name", () => ({ + default: vi.fn(), +})) + +vi.mock("../../../../utils/shell", () => ({ + getShell: vi.fn(() => "/bin/bash"), +})) + +import { getSystemInfoSection } from "../system-info" +import osName from "os-name" + +const mockOsName = osName as unknown as ReturnType + +// Extend String prototype for testing +declare global { + interface String { + toPosix(): string + } +} + +// Mock the toPosix extension +beforeEach(() => { + String.prototype.toPosix = function () { + return this.replace(/\\/g, "/") + } +}) + +afterEach(() => { + // @ts-ignore - we know this exists because we added it + delete String.prototype.toPosix +}) + +describe("getSystemInfoSection", () => { + const mockCwd = "/test/workspace" + const mockHomeDir = "/home/user" + + beforeEach(() => { + vi.spyOn(os, "homedir").mockReturnValue(mockHomeDir) + vi.spyOn(os, "platform").mockReturnValue("linux" as any) + vi.spyOn(os, "release").mockReturnValue("5.15.0") + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it("should return system info with os-name when available", () => { + mockOsName.mockReturnValue("Ubuntu 22.04") + + const result = getSystemInfoSection(mockCwd) + + expect(result).toContain("Operating System: Ubuntu 22.04") + expect(result).toContain("Default Shell: /bin/bash") + expect(result).toContain(`Home Directory: ${mockHomeDir}`) + expect(result).toContain(`Current Workspace Directory: ${mockCwd}`) + }) + + it("should fallback to platform and release when os-name throws error", () => { + mockOsName.mockImplementation(() => { + throw new Error("Command failed with ENOENT: powershell") + }) + + const result = getSystemInfoSection(mockCwd) + + expect(result).toContain("Operating System: linux 5.15.0") + expect(result).toContain("Default Shell: /bin/bash") + expect(result).toContain(`Home Directory: ${mockHomeDir}`) + expect(result).toContain(`Current Workspace Directory: ${mockCwd}`) + }) + + it("should handle Windows platform in fallback", () => { + mockOsName.mockImplementation(() => { + throw new Error("Command failed with ENOENT: powershell") + }) + vi.spyOn(os, "platform").mockReturnValue("win32" as any) + vi.spyOn(os, "release").mockReturnValue("10.0.19043") + + const result = getSystemInfoSection(mockCwd) + + expect(result).toContain("Operating System: win32 10.0.19043") + }) +}) diff --git a/src/core/prompts/sections/system-info.ts b/src/core/prompts/sections/system-info.ts index 8adc90a160e..486e46ee232 100644 --- a/src/core/prompts/sections/system-info.ts +++ b/src/core/prompts/sections/system-info.ts @@ -4,11 +4,22 @@ import osName from "os-name" import { getShell } from "../../../utils/shell" export function getSystemInfoSection(cwd: string): string { + // Try to get detailed OS name, fall back to basic info if it fails + let osInfo: string + try { + osInfo = osName() + } catch (error) { + // Fallback when os-name fails (e.g., PowerShell not available on Windows) + const platform = os.platform() + const release = os.release() + osInfo = `${platform} ${release}` + } + let details = `==== SYSTEM INFORMATION -Operating System: ${osName()} +Operating System: ${osInfo} Default Shell: ${getShell()} Home Directory: ${os.homedir().toPosix()} Current Workspace Directory: ${cwd.toPosix()} From bdd7ca5574b1285d1a0d68ebd9a31ba308f3364d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 7 Dec 2025 05:28:34 +0000 Subject: [PATCH 2/2] refactor: remove redundant toPosix setup in test file --- .../sections/__tests__/system-info.spec.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/core/prompts/sections/__tests__/system-info.spec.ts b/src/core/prompts/sections/__tests__/system-info.spec.ts index 8588fcb386f..749b53a0fd1 100644 --- a/src/core/prompts/sections/__tests__/system-info.spec.ts +++ b/src/core/prompts/sections/__tests__/system-info.spec.ts @@ -1,4 +1,3 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" import os from "os" // Mock the modules - must be hoisted before imports @@ -15,25 +14,6 @@ import osName from "os-name" const mockOsName = osName as unknown as ReturnType -// Extend String prototype for testing -declare global { - interface String { - toPosix(): string - } -} - -// Mock the toPosix extension -beforeEach(() => { - String.prototype.toPosix = function () { - return this.replace(/\\/g, "/") - } -}) - -afterEach(() => { - // @ts-ignore - we know this exists because we added it - delete String.prototype.toPosix -}) - describe("getSystemInfoSection", () => { const mockCwd = "/test/workspace" const mockHomeDir = "/home/user"