sinelaw/fresh · error
Failed to spawn shell
Error message
Failed to spawn shell: {} What it means
run_shell_command_blocking pauses the TUI and spawns the user's default shell (detect_shell) with `sh -c <command>` to run a shell command synchronously. If Command::spawn fails — shell binary missing, not executable, or OS resource limits — this error reports the underlying io::Error.
Solutions
- Check the wrapped io::Error in the message (NotFound => shell missing, PermissionDenied => chmod +x or pick another shell)
- Verify the shell resolved by detect_shell() exists: echo $SHELL and which <shell>
- Set $SHELL to an existing shell or install one in the environment/container
- Add a fallback in detect_shell() to /bin/sh when the detected shell cannot be spawned
Example fix
// before
let shell = detect_shell();
let mut child = Command::new(&shell).args(["-c", command]).hide_window().spawn()
.map_err(|e| anyhow::anyhow!("Failed to spawn shell: {}", e))?;
// after
let shell = detect_shell();
let mut child = Command::new(&shell).args(["-c", command]).hide_window().spawn()
.map_err(|e| anyhow::anyhow!("Failed to spawn shell '{}': {} (check $SHELL and PATH)", shell.display(), e))?; Defensive patterns
Strategy: validation
Validate before calling
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into());
if which::which(&shell).is_err() {
// fall back to /bin/sh before invoking the command
} Try / catch
match run_shell_command_blocking(cmd) {
Err(e) if e.to_string().starts_with("Failed to spawn shell") => {
show_error(format!("{e}; check $SHELL exists and is executable"));
}
other => other?,
} Prevention
- Keep a shell installed and on PATH in every environment the editor runs in (including containers)
- Set $SHELL correctly; prefer /bin/sh fallbacks in minimal images
- Watch for ENOENT vs EACCES in the wrapped io::Error to distinguish missing vs non-executable
- Test shell integration in sandboxed/minimal environments before rollout
When it happens
Trigger: detect_shell() resolves to a binary that doesn't exist or isn't executable (bad $SHELL, minimal container without bash); fork/exec fails due to process/file-descriptor limits; PATH lacks the shell.
Common situations: Running the editor inside minimal Docker images lacking a shell; $SHELL pointing to a removed binary; sandboxed environments (flatpak/nix) where the shell isn't on PATH; ulimit restrictions.
Related errors
- no search backend available — install ripgrep, or register…
- editor.spawnProcess is not implemented (missing…
- editor.spawnHostProcess is not implemented (missing…
- Command failed with exit code
- Failed to wait for command
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/8e0462552a331881.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/app/shell_command.rs:268
pub(crate) fn run_shell_command_blocking(&mut self, command: &str) -> anyhow::Result<()> {
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::ExecutableCommand;
use std::io::stdout;
// Suspend TUI — best-effort, nothing useful to do on failure.
#[allow(clippy::let_underscore_must_use)]
let _ = disable_raw_mode();
#[allow(clippy::let_underscore_must_use)]
let _ = stdout().execute(LeaveAlternateScreen);
let shell = detect_shell();
let mut child = Command::new(&shell)
.args(["-c", command])
.hide_window()
.spawn()
.map_err(|e| anyhow::anyhow!("Failed to spawn shell: {}", e))?;
let status = child
.wait()
.map_err(|e| anyhow::anyhow!("Failed to wait for command: {}", e))?;
// Resume TUI — best-effort, nothing useful to do on failure.
#[allow(clippy::let_underscore_must_use)]
let _ = stdout().execute(EnterAlternateScreen);
#[allow(clippy::let_underscore_must_use)]
let _ = enable_raw_mode();
// Request a full hard redraw to clear any ghost text from the external command
self.request_full_redraw();
if status.success() {
Ok(())
} else {
anyhow::bail!("Command failed with exit code: {:?}", status.code())View on GitHub (pinned to 67894ca546)