From 2cd143b8da43830dbdf83f1e3c2b69b4dd6e4005 Mon Sep 17 00:00:00 2001 From: zzq Date: Tue, 3 Mar 2026 15:55:21 +0800 Subject: [PATCH] fix: fix claude not found --- .../agent/sdk/config/ClaudeCliDiscovery.java | 111 +++++++++++++++--- 1 file changed, 94 insertions(+), 17 deletions(-) diff --git a/claude-code-sdk/src/main/java/org/springaicommunity/claude/agent/sdk/config/ClaudeCliDiscovery.java b/claude-code-sdk/src/main/java/org/springaicommunity/claude/agent/sdk/config/ClaudeCliDiscovery.java index c1a93a65..10101f92 100644 --- a/claude-code-sdk/src/main/java/org/springaicommunity/claude/agent/sdk/config/ClaudeCliDiscovery.java +++ b/claude-code-sdk/src/main/java/org/springaicommunity/claude/agent/sdk/config/ClaudeCliDiscovery.java @@ -21,6 +21,10 @@ import org.zeroturnaround.exec.ProcessExecutor; import org.zeroturnaround.exec.ProcessResult; +import java.io.BufferedReader; +import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -119,27 +123,72 @@ public static synchronized String discoverClaudePath() throws ClaudeCliNotFoundE */ private static String testAndResolveClaudeExecutable(String path) { try { - ProcessResult result = new ProcessExecutor().command(path, "--version") - .timeout(5, TimeUnit.SECONDS) - .readOutput(true) - .execute(); + // For Windows, we may need to handle scripts differently + String osName = System.getProperty("os.name").toLowerCase(); + List commandsToTry = new ArrayList<>(); - if (result.getExitValue() == 0) { - String version = result.outputUTF8().trim(); - logger.debug("Found Claude CLI at {} with version: {}", path, version); - - // If this is just a command name (no path separators), resolve to full - // path - if (!path.contains("/") && !path.contains("\\")) { - String resolvedPath = resolveCommandPath(path); - if (resolvedPath != null) { - logger.debug("Resolved command '{}' to full path: {}", path, resolvedPath); - return resolvedPath; + if (osName.contains("win")) { + // On Windows, try with explicit cmd execution for scripts + if (isScriptOnWindows(path)) { + commandsToTry.add(new String[]{"cmd", "/c", path, "--version"}); + } else { + commandsToTry.add(new String[]{path, "--version"}); + } + } else { + commandsToTry.add(new String[]{path, "--version"}); + } + + // Also try with possible extensions on Windows if it's just a command name + if (osName.contains("win") && !path.contains("/") && !path.contains("\\")) { + commandsToTry.add(new String[]{"cmd", "/c", path + ".cmd", "--version"}); + commandsToTry.add(new String[]{"cmd", "/c", path + ".bat", "--version"}); + commandsToTry.add(new String[]{"cmd", "/c", path + ".ps1", "--version"}); + } + + // Also consider resolved path from 'where' command for Windows to ensure we test the actual file + if (osName.contains("win") && !path.contains("/") && !path.contains("\\")) { + String resolvedPath = resolveCommandPath(path); + if (resolvedPath != null) { + if (isScriptOnWindows(resolvedPath)) { + commandsToTry.add(new String[]{"cmd", "/c", resolvedPath, "--version"}); + } else { + commandsToTry.add(new String[]{resolvedPath, "--version"}); } } + } + + // Try each potential command combination + for (String[] command : commandsToTry) { + try { + ProcessResult result = new ProcessExecutor().command(command) + .timeout(5, TimeUnit.SECONDS) + .readOutput(true) + .execute(); + + if (result.getExitValue() == 0) { + String version = result.outputUTF8().trim(); + logger.debug("Found Claude CLI at {} with version: {}", String.join(" ", command), version); - // Return the original path (already a full path) - return path; + // Return the original path, not the execution command + if (command.length >= 2 && !command[0].equals("cmd") && !command[1].equals("/c")) { + return command[0]; + } else if (command.length >= 3 && command[0].equals("cmd") && command[1].equals("/c")) { + // Extract the actual executable path from the cmd /c command + String actualPath = command[2]; + // Strip extension if it was added for the test + if (actualPath.endsWith(".cmd") || actualPath.endsWith(".bat") || actualPath.endsWith(".ps1")) { + String basePath = actualPath.substring(0, actualPath.lastIndexOf('.')); + if (new java.io.File(basePath).exists() && !basePath.equals(path)) { + return basePath; + } + } + return actualPath; + } + } + } catch (Exception e) { + logger.debug("Command failed: {} ({})", String.join(" ", command), e.getMessage()); + continue; + } } } catch (Exception e) { @@ -148,6 +197,34 @@ private static String testAndResolveClaudeExecutable(String path) { return null; } + /** + * Check if a given path looks like a script file on Windows which might need special handling + */ + private static boolean isScriptOnWindows(String path) { + File file = new File(path); + // If path exists and ends with script extensions, treat as script + if (file.exists() && ( + path.toLowerCase().endsWith(".sh") || + path.toLowerCase().endsWith(".ps1") || + path.toLowerCase().endsWith(".cmd") || + path.toLowerCase().endsWith(".bat"))) { + return true; + } + + // If it's a file without extension and it looks like a POSIX script (has shebang) + if (file.exists() && !file.isDirectory()) { + try (java.io.BufferedReader br = new BufferedReader(java.nio.file.Files.newBufferedReader(file.toPath()))) { + String firstLine = br.readLine(); + return firstLine != null && firstLine.startsWith("#!"); + } catch (Exception e) { + // If we can't read the file to check for shebang, assume false + return false; + } + } + + return false; + } + /** * Resolves a command name to its full path using platform-appropriate commands. Uses * 'which' on Unix/Linux/macOS and 'where' on Windows.