@@ -48,7 +48,12 @@ export async function* runClaudeCode(
4848 } )
4949
5050 process . on ( "error" , ( err ) => {
51- processState . error = err
51+ // Enhance ENOENT errors with helpful installation guidance
52+ if ( err . message . includes ( "ENOENT" ) || ( err as any ) . code === "ENOENT" ) {
53+ processState . error = createClaudeCodeNotFoundError ( claudePath , err )
54+ } else {
55+ processState . error = err
56+ }
5257 } )
5358
5459 for await ( const line of rl ) {
@@ -76,6 +81,12 @@ export async function* runClaudeCode(
7681 const { exitCode } = await process
7782 if ( exitCode !== null && exitCode !== 0 ) {
7883 const errorOutput = processState . error ?. message || processState . stderrLogs ?. trim ( )
84+
85+ // If we have a specific ENOENT error, throw that instead
86+ if ( processState . error && processState . error . name === "ClaudeCodeNotFoundError" ) {
87+ throw processState . error
88+ }
89+
7990 throw new Error (
8091 `Claude Code process exited with code ${ exitCode } .${ errorOutput ? ` Error output: ${ errorOutput } ` : "" } ` ,
8192 )
@@ -144,22 +155,31 @@ function runProcess({
144155 args . push ( "--model" , modelId )
145156 }
146157
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- } )
158+ let child
159+ try {
160+ child = execa ( claudePath , args , {
161+ stdin : "pipe" ,
162+ stdout : "pipe" ,
163+ stderr : "pipe" ,
164+ env : {
165+ ...process . env ,
166+ // Use the configured value, or the environment variable, or default to CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
167+ CLAUDE_CODE_MAX_OUTPUT_TOKENS :
168+ maxOutputTokens ?. toString ( ) ||
169+ process . env . CLAUDE_CODE_MAX_OUTPUT_TOKENS ||
170+ CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS . toString ( ) ,
171+ } ,
172+ cwd,
173+ maxBuffer : 1024 * 1024 * 1000 ,
174+ timeout : CLAUDE_CODE_TIMEOUT ,
175+ } )
176+ } catch ( error : any ) {
177+ // Handle ENOENT errors immediately when spawning the process
178+ if ( error . code === "ENOENT" || error . message ?. includes ( "ENOENT" ) ) {
179+ throw createClaudeCodeNotFoundError ( claudePath , error )
180+ }
181+ throw error
182+ }
163183
164184 // Prepare stdin data: Windows gets both system prompt & messages (avoids 8191 char limit),
165185 // other platforms get messages only (avoids Linux E2BIG error from ~128KiB execve limit)
@@ -223,3 +243,34 @@ function attemptParseChunk(data: string): ClaudeCodeMessage | null {
223243 return null
224244 }
225245}
246+
247+ /**
248+ * Creates a user-friendly error message for Claude Code ENOENT errors
249+ */
250+ function createClaudeCodeNotFoundError ( claudePath : string , originalError : Error ) : Error {
251+ const platform = os . platform ( )
252+
253+ let suggestion : string
254+ switch ( platform ) {
255+ case "darwin" : // macOS
256+ case "win32" : // Windows
257+ case "linux" :
258+ default :
259+ suggestion = "Please install Claude Code CLI:\n" +
260+ "1. Visit https://claude.ai/download to download Claude Code\n" +
261+ "2. Follow the installation instructions for your operating system\n" +
262+ "3. Ensure the 'claude' command is available in your PATH\n" +
263+ "4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'"
264+ break
265+ }
266+
267+ const errorMessage = `Claude Code executable '${ claudePath } ' not found.
268+
269+ ${ suggestion }
270+
271+ Original error: ${ originalError . message } `
272+
273+ const error = new Error ( errorMessage )
274+ error . name = "ClaudeCodeNotFoundError"
275+ return error
276+ }
0 commit comments