Skip to content

Commit c7ad873

Browse files
LouisCltclaude
andcommitted
fix(prompts): stop spawning PowerShell for the OS name on Windows
The system prompt's SYSTEM INFORMATION section called os-name on every request. On Windows its windows-release dependency runs wmic, then `powershell (Get-CimInstance Win32_OperatingSystem).caption`, through execaSync with no timeout, on the extension host's only JavaScript thread. Where wmic has been removed (recent Windows 11 builds), PowerShell starts on every request. I measured 3 to 4.5 s per call, during which the extension host is blocked. In one observed freeze, the PowerShell process did not exit, and the extension host stayed blocked: the request was never sent, Stop did nothing, and only reloading the window recovered. On Windows, read the product name from os.version(), which spawns nothing and costs well under a microsecond. Other platforms keep os-name, which does not spawn a process there, with the existing fallback. Fixes #2448 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 53cb5b5 commit c7ad873

3 files changed

Lines changed: 77 additions & 33 deletions

File tree

‎src/core/prompts/sections/__tests__/system-info.spec.ts‎

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ vi.mock("../../../../utils/shell", () => ({
1212
import { getSystemInfoSection } from "../system-info"
1313
import osName from "os-name"
1414

15-
const mockOsName = osName as unknown as ReturnType<typeof vi.fn>
15+
const mockOsName = vi.mocked(osName)
1616

1717
describe("getSystemInfoSection", () => {
1818
const mockCwd = "/test/workspace"
1919
const mockHomeDir = "/home/user"
2020

2121
beforeEach(() => {
2222
vi.spyOn(os, "homedir").mockReturnValue(mockHomeDir)
23-
vi.spyOn(os, "platform").mockReturnValue("linux" as any)
23+
vi.spyOn(os, "platform").mockReturnValue("linux")
2424
vi.spyOn(os, "release").mockReturnValue("5.15.0")
2525
})
2626

@@ -34,6 +34,7 @@ describe("getSystemInfoSection", () => {
3434

3535
afterEach(() => {
3636
vi.clearAllMocks()
37+
vi.restoreAllMocks()
3738
})
3839

3940
it("should return system info with os-name when available", () => {
@@ -47,29 +48,64 @@ describe("getSystemInfoSection", () => {
4748
expect(result).toContain(`Current Workspace Directory: ${mockCwd}`)
4849
})
4950

50-
it("should fallback to platform and release when os-name throws error", () => {
51-
mockOsName.mockImplementation(() => {
52-
throw new Error("Command failed with ENOENT: powershell")
51+
it.each([
52+
{
53+
platform: "linux" as const,
54+
release: "5.15.0",
55+
makeLookupThrow: () =>
56+
mockOsName.mockImplementation(() => {
57+
throw new Error("Command failed with ENOENT: powershell")
58+
}),
59+
},
60+
{
61+
platform: "win32" as const,
62+
release: "10.0.26300",
63+
makeLookupThrow: () =>
64+
vi.spyOn(os, "version").mockImplementation(() => {
65+
throw new Error("uv_os_uname failed")
66+
}),
67+
},
68+
])(
69+
"falls back to platform and release when the OS lookup throws on $platform",
70+
({ platform, release, makeLookupThrow }) => {
71+
vi.spyOn(os, "platform").mockReturnValue(platform)
72+
vi.spyOn(os, "release").mockReturnValue(release)
73+
makeLookupThrow()
74+
75+
const result = getSystemInfoSection(mockCwd, policyFor())
76+
77+
expect(result).toContain(`Operating System: ${platform} ${release}`)
78+
expect(result).toContain("Default Shell: /bin/bash")
79+
expect(result).toContain(`Home Directory: ${mockHomeDir}`)
80+
expect(result).toContain(`Current Workspace Directory: ${mockCwd}`)
81+
},
82+
)
83+
84+
describe("on Windows", () => {
85+
beforeEach(() => {
86+
vi.spyOn(os, "platform").mockReturnValue("win32")
87+
vi.spyOn(os, "release").mockReturnValue("10.0.26300")
5388
})
5489

55-
const result = getSystemInfoSection(mockCwd, policyFor())
90+
it("reads the product name from os.version() without calling os-name", () => {
91+
// os-name runs wmic and PowerShell synchronously on Windows; a PowerShell that never
92+
// exits freezes the extension host, so it must not be reached at all.
93+
vi.spyOn(os, "version").mockReturnValue("Windows 11 Pro")
5694

57-
expect(result).toContain("Operating System: linux 5.15.0")
58-
expect(result).toContain("Default Shell: /bin/bash")
59-
expect(result).toContain(`Home Directory: ${mockHomeDir}`)
60-
expect(result).toContain(`Current Workspace Directory: ${mockCwd}`)
61-
})
95+
const result = getSystemInfoSection(mockCwd, policyFor())
6296

63-
it("should handle Windows platform in fallback", () => {
64-
mockOsName.mockImplementation(() => {
65-
throw new Error("Command failed with ENOENT: powershell")
97+
expect(result).toContain("Operating System: Windows 11 Pro")
98+
expect(mockOsName).not.toHaveBeenCalled()
6699
})
67-
vi.spyOn(os, "platform").mockReturnValue("win32" as any)
68-
vi.spyOn(os, "release").mockReturnValue("10.0.19043")
69100

70-
const result = getSystemInfoSection(mockCwd, policyFor())
101+
it("falls back to platform and release when os.version() is empty", () => {
102+
vi.spyOn(os, "version").mockReturnValue("")
71103

72-
expect(result).toContain("Operating System: win32 10.0.19043")
104+
const result = getSystemInfoSection(mockCwd, policyFor())
105+
106+
expect(result).toContain("Operating System: win32 10.0.26300")
107+
expect(mockOsName).not.toHaveBeenCalled()
108+
})
73109
})
74110

75111
it("omits the terminal sentence when execute_command is absent", () => {

‎src/core/prompts/sections/system-info.ts‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@ import { getShell } from "../../../utils/shell"
55

66
import type { EffectiveToolPolicy } from "../tools/effective-tool-policy"
77

8+
/**
9+
* Describes the host operating system for the system prompt.
10+
*
11+
* Windows bypasses `os-name`: its `windows-release` dependency runs `wmic`, then PowerShell,
12+
* through `execaSync` with no timeout. This section is built for every request, so that call
13+
* blocks the extension host for as long as the process runs. `os.version()` reads the product
14+
* name without starting a process.
15+
*/
16+
function getOsInfo(): string {
17+
const fallback = `${os.platform()} ${os.release()}`
18+
19+
try {
20+
if (os.platform() === "win32") {
21+
return os.version() || fallback
22+
}
23+
24+
return osName()
25+
} catch {
26+
return fallback
27+
}
28+
}
29+
830
/**
931
* Builds the SYSTEM INFORMATION section of the system prompt.
1032
*
@@ -16,16 +38,7 @@ import type { EffectiveToolPolicy } from "../tools/effective-tool-policy"
1638
* @param policy The request's effective tool policy.
1739
*/
1840
export function getSystemInfoSection(cwd: string, policy: EffectiveToolPolicy): string {
19-
// Try to get detailed OS name, fall back to basic info if it fails
20-
let osInfo: string
21-
try {
22-
osInfo = osName()
23-
} catch {
24-
// Fallback when os-name fails (e.g., PowerShell not available on Windows)
25-
const platform = os.platform()
26-
const release = os.release()
27-
osInfo = `${platform} ${release}`
28-
}
41+
const osInfo = getOsInfo()
2942

3043
const executeCommandAvailable = policy.tools.has("execute_command")
3144

‎src/eslint-suppressions.json‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,6 @@
644644
"count": 55
645645
}
646646
},
647-
"core/prompts/sections/__tests__/system-info.spec.ts": {
648-
"@typescript-eslint/no-explicit-any": {
649-
"count": 2
650-
}
651-
},
652647
"core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts": {
653648
"@typescript-eslint/no-explicit-any": {
654649
"count": 14

0 commit comments

Comments
 (0)