espanso/espanso · error

unable to obtain executable path

Error message

unable to obtain executable path

What it means

get_portable_config_dir panics with 'unable to obtain executable path' when std::env::current_exe() returns Err. This function checks whether a '.espanso' folder exists next to the running executable to enable portable mode. current_exe fails when the OS cannot report the executable's path (e.g. the binary was deleted or replaced while running, or the platform facility like /proc/self/exe is unavailable).

Source

Thrown at espanso/src/path/mod.rs:153

        // Legacy macOS location in ~/Library/Preferences/espanso
        debug!(
            "detected legacy config directory location at {}",
            legacy_mac_dir.display()
        );
        Some(legacy_mac_dir)
    } else if let Some(config_dir) = get_default_config_dir() {
        debug!(
            "detected default config directory at {}",
            config_dir.display()
        );
        Some(config_dir)
    } else {
        None
    }
}

fn get_portable_config_dir() -> Option<PathBuf> {
    let espanso_exe_path = std::env::current_exe().expect("unable to obtain executable path");
    let exe_dir = espanso_exe_path.parent();
    if let Some(parent) = exe_dir {
        let config_dir = parent.join(".espanso");
        if config_dir.is_dir() {
            return Some(config_dir);
        }
    }
    None
}

fn get_home_espanso_dir() -> Option<PathBuf> {
    if let Some(home_dir) = dirs::home_dir() {
        let config_espanso_dir = home_dir.join(".espanso");
        if config_espanso_dir.is_dir() {
            return Some(config_espanso_dir);
        }
    }
    None

View on GitHub (pinned to e6c3736675)

Solutions

  1. Restart espanso from the installed binary after any update so current_exe resolves again.
  2. Mount /proc (Linux) or provide the platform facility current_exe needs if running in a minimal container.
  3. Use the --config-dir flag to bypass the portable config detection entirely.
  4. Ensure the on-disk binary is not deleted/renamed while a process is still using it (update atomically, then restart).

Example fix

// before
# updater deletes binary then notifies running instance
rm /usr/bin/espanso && killall espanso  # old process panics on config lookup
// after
mv /usr/bin/espanso /usr/bin/espanso.new && install -m755 espanso /usr/bin/espanso && killall espanso  # old exe still resolvable until restart
Defensive patterns

Strategy: try-catch

Validate before calling

if let Err(e) = std::env::current_exe() {
    eprintln!("cannot resolve executable path: {e}; use --config-dir");
}

Type guard

fn exe_path_resolvable() -> bool {
    std::env::current_exe().map(|p| p.is_file()).unwrap_or(false)
}

Try / catch

let exe_path = std::env::current_exe().unwrap_or_else(|e| {
    eprintln!("current_exe failed: {e}");
    std::process::exit(1);
});

Prevention

When it happens

Trigger: Calling get_config_dir/resolve_paths when the espanso binary has been deleted or renamed after launch (in-place upgrade), when executing via an environment where current_exe is unsupported, or in containers/jails lacking /proc. The binary itself being a bare name with no parent directory can also contribute to downstream failures.

Common situations: Package managers or update scripts replacing the espanso binary while it runs, self-update tooling deleting the executable before spawning the new one, running in minimal Docker images without /proc mounted, exotic interpreters/wrappers that break exe path resolution.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of espanso/espanso@e6c3736675 (2026-09-06). Data as JSON: /api/errors/0ec9b0652e15b526. Report an issue: GitHub.