fix: execute Claude CLI commands through WSL on Windows
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m0s
CI / Lint & Test (pull_request) Successful in 17m40s
CI / Build Linux (pull_request) Successful in 24m39s
CI / Build Windows (cross-compile) (pull_request) Successful in 42m6s

Resolves #137

Claude CLI commands (plugin list, MCP list, version check, etc.) were
being executed directly in Windows context where the `claude` binary
doesn't exist. This caused "program not found" errors across the UI.

Changes:
- Added `create_claude_command()` helper that automatically prefixes
  commands with `wsl` on Windows builds
- Updated 8 command functions to use the helper:
  - get_claude_version
  - list_plugins
  - install_plugin
  - uninstall_plugin
  - list_mcp_servers
  - remove_mcp_server
  - add_mcp_server
  - get_mcp_server_details
- Added comprehensive tests for both Windows and Linux contexts

This ensures all Claude CLI commands execute in the correct WSL
context on Windows, fixing the memory pane, plugin pane, MCP servers
pane, and CLI version detection.

 This fix was created by Hikari~ 🌸
This commit is contained in:
2026-02-08 12:04:35 -08:00
committed by Naomi Carrigan
parent e4b27bdff3
commit 269f33b52a
+47 -8
View File
@@ -49,6 +49,23 @@ fn wsl_path_to_windows(wsl_path: &str) -> Option<String> {
}
}
/// Create a Command instance for executing Claude CLI commands
/// On Windows, this will use WSL to execute the command
/// On other platforms, it executes directly
fn create_claude_command() -> std::process::Command {
#[cfg(target_os = "windows")]
{
let mut cmd = std::process::Command::new("wsl");
cmd.arg("claude");
cmd
}
#[cfg(not(target_os = "windows"))]
{
std::process::Command::new("claude")
}
}
#[tauri::command]
pub async fn start_claude(
bridge_manager: State<'_, SharedBridgeManager>,
@@ -1233,7 +1250,7 @@ pub async fn list_memory_files() -> Result<MemoryFilesResponse, String> {
pub async fn get_claude_version() -> Result<String, String> {
tracing::debug!("Getting Claude CLI version");
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("--version")
.output();
@@ -1323,7 +1340,7 @@ fn parse_plugin_list(stdout: &str) -> Vec<PluginInfo> {
pub async fn list_plugins() -> Result<Vec<PluginInfo>, String> {
tracing::debug!("Listing Claude Code plugins");
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("plugin")
.arg("list")
.output();
@@ -1352,7 +1369,7 @@ pub async fn list_plugins() -> Result<Vec<PluginInfo>, String> {
pub async fn install_plugin(plugin_name: String) -> Result<String, String> {
tracing::debug!("Installing plugin: {}", plugin_name);
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("plugin")
.arg("install")
.arg(&plugin_name)
@@ -1381,7 +1398,7 @@ pub async fn install_plugin(plugin_name: String) -> Result<String, String> {
pub async fn uninstall_plugin(plugin_name: String) -> Result<String, String> {
tracing::debug!("Uninstalling plugin: {}", plugin_name);
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("plugin")
.arg("uninstall")
.arg(&plugin_name)
@@ -1746,7 +1763,7 @@ fn parse_mcp_server_list(stdout: &str) -> Vec<McpServerInfo> {
pub async fn list_mcp_servers() -> Result<Vec<McpServerInfo>, String> {
tracing::debug!("Listing MCP servers");
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("mcp")
.arg("list")
.output();
@@ -1788,7 +1805,7 @@ pub async fn get_mcp_server(name: String) -> Result<McpServerInfo, String> {
pub async fn remove_mcp_server(name: String) -> Result<String, String> {
tracing::debug!("Removing MCP server: {}", name);
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("mcp")
.arg("remove")
.arg(&name)
@@ -1823,7 +1840,7 @@ pub async fn add_mcp_server(
) -> Result<String, String> {
tracing::debug!("Adding MCP server: {} with transport {}", name, transport);
let mut cmd = std::process::Command::new("claude");
let mut cmd = create_claude_command();
cmd.arg("mcp").arg("add");
// Add transport flag
@@ -1871,7 +1888,7 @@ pub async fn add_mcp_server(
pub async fn get_mcp_server_details(name: String) -> Result<String, String> {
tracing::debug!("Getting detailed info for MCP server: {}", name);
let output = std::process::Command::new("claude")
let output = create_claude_command()
.arg("mcp")
.arg("get")
.arg(&name)
@@ -1908,6 +1925,28 @@ mod tests {
tokio::runtime::Runtime::new().unwrap().block_on(f)
}
// ==================== create_claude_command tests ====================
#[test]
#[cfg(target_os = "windows")]
fn test_create_claude_command_windows() {
// On Windows, should create a command that uses wsl as the program with claude as first arg
let cmd = create_claude_command();
let program = cmd.get_program();
assert_eq!(program, "wsl");
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_create_claude_command_linux() {
// On Linux/Mac, should create a command that uses claude directly
let cmd = create_claude_command();
let program = cmd.get_program();
assert_eq!(program, "claude");
}
// ==================== validate_directory tests ====================
#[test]