denisidoro/navi · error

absent shell binary

Error message

absent shell binary

What it means

After successfully splitting the configured shell string into words, `shell::out()` takes the first word as the shell binary via `words.next().expect("absent shell binary")`. This cannot fail when the split succeeded with ≥1 word, but if it did (defensive branch/changed code path), it panics with "absent shell binary".

Source

Thrown at src/common/shell.rs:43

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. If you see this, check whether the shell config string splits into words — you most likely hit "empty shell command" instead ([27]) and should fix the config first
  2. Verify you are running an unmodified build of the tool
  3. Refactor to `let Some(first_cmd) = words.next() else { bail!("empty shell command") }` so both cases report config problems cleanly
  4. Set a proper `shell` value in your config

Example fix

// before
let first_cmd = words.next().expect("absent shell binary");
// after
let first_cmd = words
    .next()
    .ok_or_else(|| anyhow!("no shell binary found in `shell` config"))?;
Defensive patterns

Strategy: validation

Validate before calling

let words = shellwords::split(cfg.shell()).unwrap_or_default();
if words.first().is_none() {
    eprintln!("no shell binary token in config; fix `shell` setting");
}

Type guard

fn first_token_is_binary(cfg: &Config) -> bool {
    shellwords::split(cfg.shell())
        .ok()
        .and_then(|w| w.first().cloned())
        .map_or(false, |b| which(&b).is_ok())
}

Try / catch

// unreachable in unmodified builds; handle the sibling "empty shell command" panic instead:
std::panic::catch_unwind(shell::out).unwrap_or_else(|_| {
    eprintln!("shell config invalid");
    std::process::exit(1);
})

Prevention

When it happens

Trigger: Practically unreachable with the current code, since [27]'s expect guarantees at least one word; would trigger only if the code between the two expects changed or the iterator was advanced before `next()`.

Common situations: Custom-patched builds; refactoring that drains `words` before taking the first element; confusion while debugging the twin panic "empty shell command".

Related errors


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