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
- 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
- Verify you are running an unmodified build of the tool
- Refactor to `let Some(first_cmd) = words.next() else { bail!("empty shell command") }` so both cases report config problems cleanly
- 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
- Confirm the first token of your shell config names a real binary on PATH
- Rebuild from upstream source if you suspect a local patch altered shell::out
- Use `which <first-token>` to sanity-check the config
- Validate shellwords::split output at config load time
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
- empty shell command
- No variables received from finder
- Unable to process value
- Unable to get selection
- Unable to get query
AI-assisted analysis of denisidoro/navi@f7330b9ad5 (2026-09-03).
Data as JSON: /api/errors/ef09ac763eda069e.
Report an issue: GitHub.