Skip to content

Commit 53f19a4

Browse files
yesimmiaclaude
andcommitted
fix(windows): use PowerShell instead of /bin/zsh for shell spawning
On Windows, the app was failing to create terminals because it tried to use /bin/zsh which doesn't exist. This change adds platform-specific shell handling: - PTY sessions: Use PowerShell/cmd.exe on Windows, zsh/bash on Unix - run_shell_command: Use PowerShell on Windows - exec_shell_command: Use PowerShell with Set-Location on Windows - install_claude_code: Use PowerShell for npm, error for native install 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 58bb1af commit 53f19a4

2 files changed

Lines changed: 113 additions & 30 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5564,11 +5564,22 @@ struct ClaudeCodeVersionInfo {
55645564

55655565
/// Run a command in user's interactive login shell (to get proper PATH with nvm, etc.)
55665566
fn run_shell_command(cmd: &str) -> std::io::Result<std::process::Output> {
5567-
// Use user's default shell from $SHELL, fallback to /bin/zsh (macOS default)
5568-
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string());
5569-
std::process::Command::new(&shell)
5570-
.args(["-ilc", cmd]) // -i for interactive (loads .zshrc), -l for login, -c for command
5571-
.output()
5567+
#[cfg(windows)]
5568+
{
5569+
// On Windows, use PowerShell to run commands (better PATH handling than cmd.exe)
5570+
std::process::Command::new("powershell")
5571+
.args(["-NoProfile", "-Command", cmd])
5572+
.output()
5573+
}
5574+
5575+
#[cfg(not(windows))]
5576+
{
5577+
// Use user's default shell from $SHELL, fallback to /bin/zsh (macOS default)
5578+
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string());
5579+
std::process::Command::new(&shell)
5580+
.args(["-ilc", cmd]) // -i for interactive (loads .zshrc), -l for login, -c for command
5581+
.output()
5582+
}
55725583
}
55735584

55745585
/// Detect Claude Code installation type
@@ -5760,9 +5771,25 @@ async fn install_claude_code_version(
57605771
)
57615772
};
57625773

5763-
// Use bash directly without -il to avoid slow shell startup
5774+
// Use appropriate shell based on platform
57645775
println!("[DEBUG] cmd={}", cmd);
57655776

5777+
#[cfg(windows)]
5778+
let mut child = {
5779+
// On Windows, use PowerShell for npm commands
5780+
// Native install is not supported on Windows (uses Unix-specific tools)
5781+
if install_type_str != "npm" {
5782+
return Err("Native install is only supported on macOS/Linux. Please use npm install on Windows.".to_string());
5783+
}
5784+
Command::new("powershell")
5785+
.args(["-NoProfile", "-Command", &cmd])
5786+
.stdout(Stdio::piped())
5787+
.stderr(Stdio::piped())
5788+
.spawn()
5789+
.map_err(|e| format!("Failed to spawn: {}", e))?
5790+
};
5791+
5792+
#[cfg(not(windows))]
57665793
let mut child = Command::new("/bin/bash")
57675794
.args(["-c", &cmd])
57685795
.stdout(Stdio::piped())
@@ -6358,14 +6385,26 @@ fn read_file_base64(path: String) -> Result<String, String> {
63586385
async fn exec_shell_command(command: String, cwd: String) -> Result<String, String> {
63596386
use tokio::process::Command;
63606387

6361-
// Use user's default shell with login mode to get proper environment (API keys, etc.)
6362-
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string());
6388+
#[cfg(windows)]
6389+
let output = {
6390+
// On Windows, use PowerShell with -WorkingDirectory
6391+
Command::new("powershell")
6392+
.args(["-NoProfile", "-Command", &format!("Set-Location '{}'; {}", cwd, command)])
6393+
.output()
6394+
.await
6395+
.map_err(|e| format!("Failed to run command: {}", e))?
6396+
};
63636397

6364-
let output = Command::new(&shell)
6365-
.args(["-ilc", &format!("cd '{}' && {}", cwd, command)])
6366-
.output()
6367-
.await
6368-
.map_err(|e| format!("Failed to run command: {}", e))?;
6398+
#[cfg(not(windows))]
6399+
let output = {
6400+
// Use user's default shell with login mode to get proper environment (API keys, etc.)
6401+
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string());
6402+
Command::new(&shell)
6403+
.args(["-ilc", &format!("cd '{}' && {}", cwd, command)])
6404+
.output()
6405+
.await
6406+
.map_err(|e| format!("Failed to run command: {}", e))?
6407+
};
63696408

63706409
if output.status.success() {
63716410
Ok(String::from_utf8_lossy(&output.stdout).to_string())

src-tauri/src/pty_manager.rs

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -142,25 +142,69 @@ pub fn create_session(
142142
})
143143
.map_err(|e| format!("Failed to open PTY: {}", e))?;
144144

145-
// Determine shell (use user's default, fallback to zsh which is macOS default since Catalina)
146-
let shell_cmd = shell.unwrap_or_else(|| {
147-
std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string())
148-
});
145+
// Determine shell and build command based on platform
146+
#[cfg(windows)]
147+
let mut cmd = {
148+
// On Windows, use PowerShell as default (better than cmd.exe for modern terminals)
149+
let shell_cmd = shell.unwrap_or_else(|| {
150+
std::env::var("SHELL")
151+
.or_else(|_| std::env::var("COMSPEC"))
152+
.unwrap_or_else(|_| "powershell.exe".to_string())
153+
});
154+
155+
let is_powershell = shell_cmd.to_lowercase().contains("powershell");
156+
let is_cmd = shell_cmd.to_lowercase().contains("cmd");
157+
158+
if let Some(ref command_str) = command {
159+
let mut c = CommandBuilder::new(&shell_cmd);
160+
if is_powershell {
161+
c.arg("-NoExit");
162+
c.arg("-Command");
163+
c.arg(command_str);
164+
} else if is_cmd {
165+
c.arg("/K");
166+
c.arg(command_str);
167+
} else {
168+
// Generic: try Unix-style args
169+
c.arg("-c");
170+
c.arg(command_str);
171+
}
172+
c
173+
} else {
174+
// Interactive shell
175+
let mut c = CommandBuilder::new(&shell_cmd);
176+
if is_powershell {
177+
c.arg("-NoExit");
178+
} else if is_cmd {
179+
c.arg("/K");
180+
}
181+
c
182+
}
183+
};
149184

150-
// Build command: either run custom command via login shell, or just start shell
151-
// Use -ilc (interactive + login) to load user's shell config (~/.zshrc, ~/.bashrc)
152-
// This ensures PATH includes nvm, homebrew, npm global, etc.
153-
let mut cmd = if let Some(ref command_str) = command {
154-
let mut c = CommandBuilder::new(&shell_cmd);
155-
c.arg("-ilc");
156-
c.arg(command_str);
157-
c
158-
} else {
159-
// Interactive shell: use -il for login mode (loads profile/rc files)
160-
let mut c = CommandBuilder::new(&shell_cmd);
161-
c.arg("-il");
162-
c
185+
#[cfg(not(windows))]
186+
let mut cmd = {
187+
// On Unix, use user's default shell, fallback to zsh (macOS default since Catalina)
188+
let shell_cmd = shell.unwrap_or_else(|| {
189+
std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string())
190+
});
191+
192+
// Build command: either run custom command via login shell, or just start shell
193+
// Use -ilc (interactive + login) to load user's shell config (~/.zshrc, ~/.bashrc)
194+
// This ensures PATH includes nvm, homebrew, npm global, etc.
195+
if let Some(ref command_str) = command {
196+
let mut c = CommandBuilder::new(&shell_cmd);
197+
c.arg("-ilc");
198+
c.arg(command_str);
199+
c
200+
} else {
201+
// Interactive shell: use -il for login mode (loads profile/rc files)
202+
let mut c = CommandBuilder::new(&shell_cmd);
203+
c.arg("-il");
204+
c
205+
}
163206
};
207+
164208
cmd.cwd(&cwd);
165209

166210
// Set proper TERM for xterm.js

0 commit comments

Comments
 (0)