denisidoro/navi · error

External command failed: {err}

Error message

External command failed:
 {err}

What it means

In `finder::parse`, after navi spawns an external fuzzy-finder process (fzf, sk, etc.) and reads its output, any exit code other than the handled ones (e.g. 0 for selection, 130 for user abort) triggers a panic including the child's stderr. This means the finder itself failed rather than the user simply canceling.

Source

Thrown at src/finder/mod.rs:45

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "fzf" => Ok(FinderChoice::Fzf),
            "skim" => Ok(FinderChoice::Skim),
            _ => Err("no match"),
        }
    }
}

fn parse(out: Output, opts: Opts) -> Result<String> {
    let text = match out.status.code() {
        Some(0) | Some(1) | Some(2) => {
            String::from_utf8(out.stdout).context("Invalid utf8 received from finder")?
        }
        Some(130) => process::exit(130),
        _ => {
            let err = String::from_utf8(out.stderr)
                .unwrap_or_else(|_| "<stderr contains invalid UTF-8>".to_owned());
            panic!("External command failed:\n {err}")
        }
    };

    let output = post::parse_output_single(text, opts.suggestion_type)?;
    post::process(output, opts.column, opts.delimiter.as_deref(), opts.map)
}

impl FinderChoice {
    fn check_fzf_version() -> Option<(u32, u32, u32)> {
        let output = Command::new("fzf").arg("--version").output().ok()?.stdout;
        let version_string = String::from_utf8(output).ok()?;
        let version_parts: Vec<_> = version_string.split('.').collect();
        if version_parts.len() == 3 {
            let major = version_parts[0].parse().ok()?;
            let minor = version_parts[1].parse().ok()?;
            let patch = version_parts[2].split_whitespace().next()?.parse().ok()?;
            Some((major, minor, patch))
        } else {

View on GitHub (pinned to f7330b9ad5)

Solutions

  1. Run the finder command manually (`fzf --version`, then the exact invocation) to see the underlying stderr in the panic message
  2. Update fzf to a recent version (navi relies on modern fzf features)
  3. Fix or remove broken options in navi's finder override settings
  4. Verify the configured finder binary is the real one (`which fzf`) and not a failing wrapper

Example fix

// before: navi panics with the finder's stderr
$ navi
External command failed:
 unknown option: --height40
// after
$ fzf --version && fzf --height 40%   # reproduce manually
$ brew upgrade fzf   # or apt install newer fzf
$ navi
Defensive patterns

Strategy: try-catch

Validate before calling

use std::process::Command;
fn finder_available() -> bool {
    Command::new("fzf").arg("--version").output()
        .map(|o| o.status.success()).unwrap_or(false)
}
if !finder_available() {
    eprintln!("fzf is missing or broken; install/upgrade it");
    std::process::exit(1);
}

Type guard

fn finder_output_ok(out: &std::process::Output) -> bool {
    matches!(out.status.code(), Some(0) | Some(130))
}

Try / catch

match navi::finder::call(opts) {
    Ok(selection) => use_selection(selection),
    Err(e) if e.to_string().contains("External command failed") => {
        eprintln!("finder failed: {}", e);
        eprintln!("check NAVI_FZF_OVERRIDES and your fzf version");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `call` -> `parse` when the spawned finder exits with an unexpected status: fzf not actually installed (shell falls back or errors), fzf crashing, invalid finder options/flags passed via finder configuration, or stderr produced by an incompatible finder version.

Common situations: fzf version too old for the flags navi passes, a custom `NAVI_FZF_OVERRIDES`/finder config with broken options, an aliased or wrapper 'fzf' that fails, or terminal issues (no TTY) causing the finder to abort.

Related errors


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