Skip to content

Commit 0423aa4

Browse files
committed
Validate the default shell
1 parent b48b0be commit 0423aa4

File tree

6 files changed

+131
-57
lines changed

6 files changed

+131
-57
lines changed

pnpm-lock.yaml

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import osName from "os-name"
33

44
import { getShell } from "../../../utils/shell"
55

6-
export function getSystemInfoSection(cwd: string): string {
6+
export async function getSystemInfoSection(cwd: string): Promise<string> {
7+
const shell = await getShell()
78
let details = `====
89
910
SYSTEM INFORMATION
1011
1112
Operating System: ${osName()}
12-
Default Shell: ${getShell()}
13+
Default Shell: ${shell}
1314
Home Directory: ${os.homedir().toPosix()}
1415
Current Workspace Directory: ${cwd.toPosix()}
1516

src/core/prompts/system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ ${modesSection}
120120
121121
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, codeIndexManager)}
122122
123-
${getSystemInfoSection(cwd)}
123+
${await getSystemInfoSection(cwd)}
124124
125125
${getObjectiveSection(codeIndexManager, experiments)}
126126

src/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@
497497
"uuid": "^11.1.0",
498498
"vscode-material-icons": "^0.1.1",
499499
"web-tree-sitter": "^0.25.6",
500+
"which": "^5.0.0",
500501
"workerpool": "^9.2.0",
501502
"yaml": "^2.8.0",
502503
"zod": "^3.25.61"
@@ -522,6 +523,7 @@
522523
"@types/tmp": "^0.2.6",
523524
"@types/turndown": "^5.0.5",
524525
"@types/vscode": "^1.84.0",
526+
"@types/which": "^3.0.4",
525527
"@vscode/test-electron": "^2.5.2",
526528
"@vscode/vsce": "3.3.2",
527529
"esbuild": "^0.25.0",

src/utils/__tests__/shell.spec.ts

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ vi.mock("os", () => ({
77
userInfo: vi.fn(() => ({ shell: null })),
88
}))
99

10+
// Mock the which module
11+
vi.mock("which", () => ({
12+
default: vi.fn(),
13+
}))
14+
15+
import which from "which"
16+
1017
describe("Shell Detection Tests", () => {
1118
let originalPlatform: string
1219
let originalEnv: NodeJS.ProcessEnv
@@ -40,6 +47,12 @@ describe("Shell Detection Tests", () => {
4047

4148
// Reset userInfo mock to default
4249
vi.mocked(userInfo).mockReturnValue({ shell: null } as any)
50+
51+
// Mock which to always resolve paths successfully for tests
52+
vi.mocked(which).mockImplementation(async (cmd: string) => {
53+
// Return the command as-is to simulate successful resolution
54+
return cmd
55+
})
4356
})
4457

4558
afterEach(() => {
@@ -58,66 +71,66 @@ describe("Shell Detection Tests", () => {
5871
Object.defineProperty(process, "platform", { value: "win32" })
5972
})
6073

61-
it("uses explicit PowerShell 7 path from VS Code config (profile path)", () => {
74+
it("uses explicit PowerShell 7 path from VS Code config (profile path)", async () => {
6275
mockVsCodeConfig("windows", "PowerShell", {
6376
PowerShell: { path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe" },
6477
})
65-
expect(getShell()).toBe("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
78+
expect(await getShell()).toBe("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
6679
})
6780

68-
it("uses PowerShell 7 path if source is 'PowerShell' but no explicit path", () => {
81+
it("uses PowerShell 7 path if source is 'PowerShell' but no explicit path", async () => {
6982
mockVsCodeConfig("windows", "PowerShell", {
7083
PowerShell: { source: "PowerShell" },
7184
})
72-
expect(getShell()).toBe("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
85+
expect(await getShell()).toBe("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
7386
})
7487

75-
it("falls back to legacy PowerShell if profile includes 'powershell' but no path/source", () => {
88+
it("falls back to legacy PowerShell if profile includes 'powershell' but no path/source", async () => {
7689
mockVsCodeConfig("windows", "PowerShell", {
7790
PowerShell: {},
7891
})
79-
expect(getShell()).toBe("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")
92+
expect(await getShell()).toBe("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")
8093
})
8194

82-
it("uses WSL bash when profile indicates WSL source", () => {
95+
it("uses WSL bash when profile indicates WSL source", async () => {
8396
mockVsCodeConfig("windows", "WSL", {
8497
WSL: { source: "WSL" },
8598
})
86-
expect(getShell()).toBe("/bin/bash")
99+
expect(await getShell()).toBe("/bin/bash")
87100
})
88101

89-
it("uses WSL bash when profile name includes 'wsl'", () => {
102+
it("uses WSL bash when profile name includes 'wsl'", async () => {
90103
mockVsCodeConfig("windows", "Ubuntu WSL", {
91104
"Ubuntu WSL": {},
92105
})
93-
expect(getShell()).toBe("/bin/bash")
106+
expect(await getShell()).toBe("/bin/bash")
94107
})
95108

96-
it("defaults to cmd.exe if no special profile is matched", () => {
109+
it("defaults to cmd.exe if no special profile is matched", async () => {
97110
mockVsCodeConfig("windows", "CommandPrompt", {
98111
CommandPrompt: {},
99112
})
100-
expect(getShell()).toBe("C:\\Windows\\System32\\cmd.exe")
113+
expect(await getShell()).toBe("C:\\Windows\\System32\\cmd.exe")
101114
})
102115

103-
it("handles undefined profile gracefully", () => {
116+
it("handles undefined profile gracefully", async () => {
104117
// Mock a case where defaultProfileName exists but the profile doesn't
105118
mockVsCodeConfig("windows", "NonexistentProfile", {})
106-
expect(getShell()).toBe("C:\\Windows\\System32\\cmd.exe")
119+
expect(await getShell()).toBe("C:\\Windows\\System32\\cmd.exe")
107120
})
108121

109-
it("respects userInfo() if no VS Code config is available", () => {
122+
it("respects userInfo() if no VS Code config is available", async () => {
110123
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
111124
vi.mocked(userInfo).mockReturnValue({ shell: "C:\\Custom\\PowerShell.exe" } as any)
112125

113-
expect(getShell()).toBe("C:\\Custom\\PowerShell.exe")
126+
expect(await getShell()).toBe("C:\\Custom\\PowerShell.exe")
114127
})
115128

116-
it("respects an odd COMSPEC if no userInfo shell is available", () => {
129+
it("respects an odd COMSPEC if no userInfo shell is available", async () => {
117130
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
118131
process.env.COMSPEC = "D:\\CustomCmd\\cmd.exe"
119132

120-
expect(getShell()).toBe("D:\\CustomCmd\\cmd.exe")
133+
expect(await getShell()).toBe("D:\\CustomCmd\\cmd.exe")
121134
})
122135
})
123136

@@ -129,28 +142,28 @@ describe("Shell Detection Tests", () => {
129142
Object.defineProperty(process, "platform", { value: "darwin" })
130143
})
131144

132-
it("uses VS Code profile path if available", () => {
145+
it("uses VS Code profile path if available", async () => {
133146
mockVsCodeConfig("osx", "MyCustomShell", {
134147
MyCustomShell: { path: "/usr/local/bin/fish" },
135148
})
136-
expect(getShell()).toBe("/usr/local/bin/fish")
149+
expect(await getShell()).toBe("/usr/local/bin/fish")
137150
})
138151

139-
it("falls back to userInfo().shell if no VS Code config is available", () => {
152+
it("falls back to userInfo().shell if no VS Code config is available", async () => {
140153
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
141154
vi.mocked(userInfo).mockReturnValue({ shell: "/opt/homebrew/bin/zsh" } as any)
142-
expect(getShell()).toBe("/opt/homebrew/bin/zsh")
155+
expect(await getShell()).toBe("/opt/homebrew/bin/zsh")
143156
})
144157

145-
it("falls back to SHELL env var if no userInfo shell is found", () => {
158+
it("falls back to SHELL env var if no userInfo shell is found", async () => {
146159
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
147160
process.env.SHELL = "/usr/local/bin/zsh"
148-
expect(getShell()).toBe("/usr/local/bin/zsh")
161+
expect(await getShell()).toBe("/usr/local/bin/zsh")
149162
})
150163

151-
it("falls back to /bin/zsh if no config, userInfo, or env variable is set", () => {
164+
it("falls back to /bin/zsh if no config, userInfo, or env variable is set", async () => {
152165
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
153-
expect(getShell()).toBe("/bin/zsh")
166+
expect(await getShell()).toBe("/bin/zsh")
154167
})
155168
})
156169

@@ -162,61 +175,61 @@ describe("Shell Detection Tests", () => {
162175
Object.defineProperty(process, "platform", { value: "linux" })
163176
})
164177

165-
it("uses VS Code profile path if available", () => {
178+
it("uses VS Code profile path if available", async () => {
166179
mockVsCodeConfig("linux", "CustomProfile", {
167180
CustomProfile: { path: "/usr/bin/fish" },
168181
})
169-
expect(getShell()).toBe("/usr/bin/fish")
182+
expect(await getShell()).toBe("/usr/bin/fish")
170183
})
171184

172-
it("falls back to userInfo().shell if no VS Code config is available", () => {
185+
it("falls back to userInfo().shell if no VS Code config is available", async () => {
173186
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
174187
vi.mocked(userInfo).mockReturnValue({ shell: "/usr/bin/zsh" } as any)
175-
expect(getShell()).toBe("/usr/bin/zsh")
188+
expect(await getShell()).toBe("/usr/bin/zsh")
176189
})
177190

178-
it("falls back to SHELL env var if no userInfo shell is found", () => {
191+
it("falls back to SHELL env var if no userInfo shell is found", async () => {
179192
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
180193
process.env.SHELL = "/usr/bin/fish"
181-
expect(getShell()).toBe("/usr/bin/fish")
194+
expect(await getShell()).toBe("/usr/bin/fish")
182195
})
183196

184-
it("falls back to /bin/bash if nothing is set", () => {
197+
it("falls back to /bin/bash if nothing is set", async () => {
185198
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
186-
expect(getShell()).toBe("/bin/bash")
199+
expect(await getShell()).toBe("/bin/bash")
187200
})
188201
})
189202

190203
// --------------------------------------------------------------------------
191204
// Unknown Platform & Error Handling
192205
// --------------------------------------------------------------------------
193206
describe("Unknown Platform / Error Handling", () => {
194-
it("falls back to /bin/sh for unknown platforms", () => {
207+
it("falls back to /bin/sh for unknown platforms", async () => {
195208
Object.defineProperty(process, "platform", { value: "sunos" })
196209
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
197-
expect(getShell()).toBe("/bin/sh")
210+
expect(await getShell()).toBe("/bin/sh")
198211
})
199212

200-
it("handles VS Code config errors gracefully, falling back to userInfo shell if present", () => {
213+
it("handles VS Code config errors gracefully, falling back to userInfo shell if present", async () => {
201214
Object.defineProperty(process, "platform", { value: "linux" })
202215
vscode.workspace.getConfiguration = () => {
203216
throw new Error("Configuration error")
204217
}
205218
vi.mocked(userInfo).mockReturnValue({ shell: "/bin/bash" } as any)
206-
expect(getShell()).toBe("/bin/bash")
219+
expect(await getShell()).toBe("/bin/bash")
207220
})
208221

209-
it("handles userInfo errors gracefully, falling back to environment variable if present", () => {
222+
it("handles userInfo errors gracefully, falling back to environment variable if present", async () => {
210223
Object.defineProperty(process, "platform", { value: "darwin" })
211224
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
212225
vi.mocked(userInfo).mockImplementation(() => {
213226
throw new Error("userInfo error")
214227
})
215228
process.env.SHELL = "/bin/zsh"
216-
expect(getShell()).toBe("/bin/zsh")
229+
expect(await getShell()).toBe("/bin/zsh")
217230
})
218231

219-
it("falls back fully to default shell paths if everything fails", () => {
232+
it("falls back fully to default shell paths if everything fails", async () => {
220233
Object.defineProperty(process, "platform", { value: "linux" })
221234
vscode.workspace.getConfiguration = () => {
222235
throw new Error("Configuration error")
@@ -225,7 +238,7 @@ describe("Shell Detection Tests", () => {
225238
throw new Error("userInfo error")
226239
})
227240
delete process.env.SHELL
228-
expect(getShell()).toBe("/bin/bash")
241+
expect(await getShell()).toBe("/bin/bash")
229242
})
230243
})
231244
})

0 commit comments

Comments
 (0)