denisidoro/navi · critical

empty shell command

Error message

empty shell command

What it means

`shell::out()` builds the `Command` used for shell-outs (e.g. by `copy`) from the configured shell string (`CONFIG.shell()`). It splits the string with `shellwords::split` and panics with "empty shell command" if splitting yields no words — typically because the configured shell string is empty or unparseable to zero tokens.

Source

Thrown at src/common/shell.rs:41

    source: anyhow::Error,
}

impl ShellSpawnError {
    pub fn new<SourceError, T>(command: T, source: SourceError) -> Self
    where
        SourceError: std::error::Error + Sync + Send + 'static,
        T: Into<String>,
    {
        ShellSpawnError {
            command: command.into(),
            source: source.into(),
        }
    }
}

pub fn out() -> Command {
    let words_str = CONFIG.shell();
    let mut words_vec = shellwords::split(&words_str).expect("empty shell command");
    let mut words = words_vec.iter_mut();
    let first_cmd = words.next().expect("absent shell binary");
    let mut cmd = Command::new(first_cmd);
    cmd.args(words);
    let dash_c = if words_str.contains("cmd.exe") { "/c" } else { "-c" };
    cmd.arg(dash_c);
    cmd
}

View on GitHub (pinned to f7330b9ad5)

Solutions

  1. Set a valid shell in your config (e.g. `shell = "bash"` or the full `sh -c` style string your platform needs)
  2. Inspect the resolved config (`CONFIG.shell()`) — check the relevant environment/config file actually defines it
  3. Avoid whitespace-only or quote-only values for the shell setting
  4. Patch `out()` to return a `Result` with a clear config error instead of panicking

Example fix

// before
let mut words_vec = shellwords::split(&words_str).expect("empty shell command");
// after
let mut words_vec = shellwords::split(&words_str)
    .expect("empty shell command")
    .ok_or_else(|| anyhow!("`shell` config is empty; set a valid shell (e.g. bash)"))?
Defensive patterns

Strategy: validation

Validate before calling

let shell = config.get("shell").unwrap_or_default();
if shell.split_whitespace().count() == 0 {
    eprintln!("config error: `shell` must be a non-empty command (e.g. bash)");
    std::process::exit(1);
}

Type guard

fn has_valid_shell(cfg: &Config) -> bool {
    cfg.shell().trim().split_whitespace().next().is_some()
}

Try / catch

match std::panic::catch_unwind(shell::out) {
    Ok(cmd) => cmd,
    Err(_) => { eprintln!("shell command empty; set `shell` in your config"); std::process::exit(1); }
}

Prevention

When it happens

Trigger: Calling `shell::out()` (directly or via `copy`/other shell-out helpers) when `CONFIG.shell()` resolves to an empty string, or a string that shellwords reduces to zero tokens (only quotes/whitespace).

Common situations: Empty or blank `shell` entry in the tool's config file; config load defaulting shell to empty; a config migration dropping the shell key; quoting mistakes that shellwords strips entirely.

Related errors


AI-assisted analysis of denisidoro/navi@f7330b9ad5 (2026-09-03). Data as JSON: /api/errors/d9ab89ac4bce21f7. Report an issue: GitHub.