herdrdev/herdr · error · io::Error

login shell {shell:?} is not executable

Error message

login shell {shell:?} is not executable

What it means

Herdr validates the shell used for login-mode panes. When the configured shell string contains a path separator (e.g. /bin/zsh or C:\bin\bash.exe), it must point to an existing executable file, otherwise this NotFound error is returned. It is a configuration-validation error raised before spawning the pane.

Source

Thrown at src/pane.rs:1454

    }
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        metadata.permissions().mode() & 0o111 != 0
    }
    #[cfg(not(unix))]
    {
        true
    }
}

fn resolve_shell_for_login_mode(shell: &str) -> io::Result<String> {
    if shell.contains(std::path::MAIN_SEPARATOR) {
        let path = Path::new(shell);
        return is_executable_file(path)
            .then(|| shell.to_string())
            .ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::NotFound,
                    format!("login shell {shell:?} is not executable"),
                )
            });
    }

    std::env::var_os("PATH")
        .and_then(|path| {
            std::env::split_paths(&path)
                .map(|dir| dir.join(shell))
                .find(|candidate| is_executable_file(candidate))
        })
        .and_then(|path| path.into_os_string().into_string().ok())
        .ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::NotFound,
                format!("login shell {shell:?} was not found on PATH"),
            )

View on GitHub (pinned to f457cff4f2)

Solutions

  1. Verify the path exists and is executable: ls -l /path/to/shell and run it directly
  2. Fix the shell setting in your shell/SSH/passwd configuration to point to the real executable
  3. If the binary moved (e.g. Homebrew), update to the new path or symlink the old location
  4. As a last resort set an absolute path to /bin/sh or /bin/bash

Example fix

// before
shell = "/opt/homebrew/bin/fish-4.0"   // removed by upgrade
// after
shell = "/opt/homebrew/bin/fish"
Defensive patterns

Strategy: validation

Validate before calling

let shell_path = std::path::Path::new(shell);
if shell.contains(std::path::MAIN_SEPARATOR)
    && !(shell_path.is_file()
        && is_executable(shell_path))
{
    return Err(format!("login shell {shell:?} is not executable").into());
}

Try / catch

match resolve_shell_for_login_mode(shell) {
    Ok(resolved) => { /* proceed */ }
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
        // fall back to a known-good shell or surface a config error
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling pane creation/login-mode shell resolution with a shell value containing a path separator that either does not exist, is a directory, or lacks executable permission. On Unix this fails is_executable_file (missing x bit or ENOENT); on Windows it fails if the file is absent or not a valid executable.

Common situations: User's login shell from /etc/passwd or $SHELL points to a removed/renamed binary (e.g. a brew-installed fish upgraded paths), a shell set via config with a typo, or a path copied from another machine. Also Nix/store paths that were garbage-collected.

Related errors


AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28). Data as JSON: /api/errors/2a84cbfc4385d230. Report an issue: GitHub.