gitbutlerapp/gitbutler · warning · anyhow::Error
command not found
Error message
command not found
What it means
Shown in the `but` TUI command mode: input typed after `:` (but command) or `!` (shell command) is shell-split with `shell_words::split` and the first token is spawned with `Command::new(binary)`. When the OS reports `ErrorKind::NotFound` — the binary is not on PATH or the path is wrong — the TUI pushes a transient 'command not found' message and keeps running; other spawn errors propagate with 'failed to start command'.
Source
Thrown at crates/but/src/command/legacy/status/tui/app/command_mode.rs:221
return Ok(());
}
};
let Some(binary) = args.next() else {
return Ok(());
};
let mut cmd = Command::new(binary);
cmd.args(args);
cmd
}
};
cmd.current_dir(ctx.workdir_or_fail()?);
let mut child = match cmd.spawn() {
Ok(child) => child,
Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
self.push_transient_error(anyhow!("command not found"));
return Ok(());
} else {
return Err(err).context("failed to start command");
}
}
};
let status = child.wait()?;
if !IN_TEST {
out.prompt_single_line("\npress enter to continue...")?;
}
if status.success() {
messages.extend([
Message::EnterNormalModeAfterConfirmingOperation,
Message::Reload(None, ReloadCause::Mutation),
]);
} else {View on GitHub (pinned to caf1f223d3)
Solutions
- Check the spelling of the command
- Verify the binary is reachable in that environment: run `!which <cmd>` from the TUI or echo $PATH from a shell that matches the launch environment
- Use an absolute path for the command (e.g. `!/usr/bin/git status`) to take PATH out of the equation
- Launch `but` from your normal shell (with full PATH) rather than from a GUI/app launcher
Example fix
# before: not on the TUI's PATH !gitui # after: absolute path, or fix PATH !/opt/homebrew/bin/gitui
Defensive patterns
Strategy: validation
Validate before calling
let binary = args[0].as_ref();
if !binary.to_string_lossy().contains(std::path::MAIN_SEPARATOR_str())
&& which::which(binary).is_err()
{
self.push_transient_error(anyhow!("command not found: {binary:?} (check PATH)"));
return Ok(());
} Type guard
fn is_not_found(err: &std::io::Error) -> bool {
err.kind() == std::io::ErrorKind::NotFound
} Try / catch
match cmd.spawn() {
Ok(child) => { let _ = child.wait()?; }
Err(err) if is_not_found(&err) => {
self.push_transient_error(anyhow!("command not found"));
return Ok(());
}
Err(err) => return Err(err).context("failed to start command"),
} Prevention
- Type absolute paths for commands not guaranteed to be on PATH (`!/usr/bin/git ...`)
- Launch the TUI from a normal login shell so PATH is fully populated
- Verify availability from inside the TUI with `!which <cmd>` before relying on it
- Check spelling first — shell_words splitting means typos surface as NotFound
When it happens
Trigger: Typing a shell command (`!...`) whose binary is not installed or not on the TUI process's PATH, e.g. `!gitx` with gitx missing, or `!./tool` from the wrong directory.
Common situations: Launching the TUI from a GUI launcher or minimal-PATH environment where usual shell PATH entries are absent; typo in the command name; tool simply not installed; relative path not resolved because cwd differs from expectation.
Related errors
- The path {} does not exist
- The path {} is not a directory
- Could not find {kind} CLI id '{short_id}' in IdMap
- CLI id '{short_id}' is ambiguous for {kind} in IdMap
- Failed to parse diff header
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/a0244ae7061b2799.
Report an issue: GitHub.