Skip to content

Commit 08188ad

Browse files
committed
fix: hide Test ErrorBoundary button in production and improve claude CLI error handling
- Hide the Test ErrorBoundary button in production builds by checking process.env.NODE_ENV - Add better error handling for missing claude CLI command with helpful installation instructions - Add tests for ENOENT error handling in claude-code integration Fixes #6218
1 parent d62a260 commit 08188ad

File tree

3 files changed

+125
-20
lines changed

3 files changed

+125
-20
lines changed

src/integrations/claude-code/__tests__/run.spec.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,94 @@ describe("runClaudeCode", () => {
289289
consoleErrorSpy.mockRestore()
290290
await generator.return(undefined)
291291
})
292+
293+
test("should handle ENOENT error with helpful message", async () => {
294+
const { runClaudeCode } = await import("../run")
295+
296+
// Mock execa to throw ENOENT error
297+
const enoentError = new Error("spawn claude ENOENT") as any
298+
enoentError.code = "ENOENT"
299+
mockExeca.mockImplementationOnce(() => {
300+
throw enoentError
301+
})
302+
303+
const options = {
304+
systemPrompt: "You are a helpful assistant",
305+
messages: [{ role: "user" as const, content: "Hello" }],
306+
}
307+
308+
const generator = runClaudeCode(options)
309+
310+
// Try to consume the generator
311+
try {
312+
await generator.next()
313+
expect.fail("Should have thrown an error")
314+
} catch (error: any) {
315+
expect(error.message).toContain("Claude CLI command not found")
316+
expect(error.message).toContain("Please ensure the Claude CLI is installed")
317+
expect(error.message).toContain("https://github.com/anthropics/claude-cli")
318+
}
319+
320+
// Clean up
321+
await generator.return(undefined)
322+
})
323+
324+
test("should handle ENOENT error with custom path in message", async () => {
325+
const { runClaudeCode } = await import("../run")
326+
327+
// Mock execa to throw ENOENT error
328+
const enoentError = new Error("spawn /custom/claude ENOENT") as any
329+
enoentError.code = "ENOENT"
330+
mockExeca.mockImplementationOnce(() => {
331+
throw enoentError
332+
})
333+
334+
const options = {
335+
systemPrompt: "You are a helpful assistant",
336+
messages: [{ role: "user" as const, content: "Hello" }],
337+
path: "/custom/claude",
338+
}
339+
340+
const generator = runClaudeCode(options)
341+
342+
// Try to consume the generator
343+
try {
344+
await generator.next()
345+
expect.fail("Should have thrown an error")
346+
} catch (error: any) {
347+
expect(error.message).toContain("Claude CLI command not found at '/custom/claude'")
348+
}
349+
350+
// Clean up
351+
await generator.return(undefined)
352+
})
353+
354+
test("should rethrow non-ENOENT errors unchanged", async () => {
355+
const { runClaudeCode } = await import("../run")
356+
357+
// Mock execa to throw a different error
358+
const otherError = new Error("Some other error")
359+
mockExeca.mockImplementationOnce(() => {
360+
throw otherError
361+
})
362+
363+
const options = {
364+
systemPrompt: "You are a helpful assistant",
365+
messages: [{ role: "user" as const, content: "Hello" }],
366+
}
367+
368+
const generator = runClaudeCode(options)
369+
370+
// Try to consume the generator
371+
try {
372+
await generator.next()
373+
expect.fail("Should have thrown an error")
374+
} catch (error: any) {
375+
expect(error.message).toBe("Some other error")
376+
expect(error.message).not.toContain("Claude CLI command not found")
377+
}
378+
379+
// Clean up
380+
await generator.return(undefined)
381+
})
292382
})

src/integrations/claude-code/run.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -144,22 +144,35 @@ function runProcess({
144144
args.push("--model", modelId)
145145
}
146146

147-
const child = execa(claudePath, args, {
148-
stdin: "pipe",
149-
stdout: "pipe",
150-
stderr: "pipe",
151-
env: {
152-
...process.env,
153-
// Use the configured value, or the environment variable, or default to CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
154-
CLAUDE_CODE_MAX_OUTPUT_TOKENS:
155-
maxOutputTokens?.toString() ||
156-
process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS ||
157-
CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS.toString(),
158-
},
159-
cwd,
160-
maxBuffer: 1024 * 1024 * 1000,
161-
timeout: CLAUDE_CODE_TIMEOUT,
162-
})
147+
let child
148+
try {
149+
child = execa(claudePath, args, {
150+
stdin: "pipe",
151+
stdout: "pipe",
152+
stderr: "pipe",
153+
env: {
154+
...process.env,
155+
// Use the configured value, or the environment variable, or default to CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
156+
CLAUDE_CODE_MAX_OUTPUT_TOKENS:
157+
maxOutputTokens?.toString() ||
158+
process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS ||
159+
CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS.toString(),
160+
},
161+
cwd,
162+
maxBuffer: 1024 * 1024 * 1000,
163+
timeout: CLAUDE_CODE_TIMEOUT,
164+
})
165+
} catch (error: any) {
166+
if (error.code === "ENOENT") {
167+
throw new Error(
168+
`Claude CLI command not found at '${claudePath}'. ` +
169+
`Please ensure the Claude CLI is installed and available in your system PATH. ` +
170+
`You can install it from https://github.com/anthropics/claude-cli or ` +
171+
`specify a custom path in the extension settings.`,
172+
)
173+
}
174+
throw error
175+
}
163176

164177
// Prepare stdin data: Windows gets both system prompt & messages (avoids 8191 char limit),
165178
// other platforms get messages only (avoids Linux E2BIG error from ~128KiB execve limit)

webview-ui/src/components/settings/About.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,12 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro
110110
</Button>
111111

112112
{/* Test button for ErrorBoundary - only visible in development */}
113-
<Button variant="destructive" onClick={triggerTestError} className="w-auto">
114-
<TriangleAlert className="p-0.5" />
115-
Test ErrorBoundary
116-
</Button>
113+
{process.env.NODE_ENV !== "production" && (
114+
<Button variant="destructive" onClick={triggerTestError} className="w-auto">
115+
<TriangleAlert className="p-0.5" />
116+
Test ErrorBoundary
117+
</Button>
118+
)}
117119
</div>
118120
</Section>
119121
</div>

0 commit comments

Comments
 (0)