espanso/espanso · critical

unable to create config directory

Error message

unable to create config directory

What it means

espanso's resolve_paths panics with 'unable to create config directory' when it cannot create the default config directory (e.g. ~/.config/espanso on Linux, ~/Library/Application Support/espanso on macOS, %APPDATA%\espanso on Windows). This is reached only when no existing config directory was detected anywhere (portable, $HOME/.espanso, $HOME/.config/espanso, legacy macOS, default) and create_dir_all fails. It aborts the whole process because espanso cannot run without a config directory.

Source

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

    pub packages: PathBuf,

    pub is_portable_mode: bool,
}

pub fn resolve_paths(
    force_config_dir: Option<&Path>,
    force_package_dir: Option<&Path>,
    force_runtime_dir: Option<&Path>,
) -> Paths {
    let config_dir = if let Some(config_dir) = force_config_dir {
        config_dir.to_path_buf()
    } else if let Some(config_dir) = get_config_dir() {
        config_dir
    } else {
        // Create the config directory if not already present
        let config_dir = get_default_config_path();
        info!("creating config directory in {}", config_dir.display());
        create_dir_all(&config_dir).expect("unable to create config directory");
        config_dir
    };

    let runtime_dir = if let Some(runtime_dir) = force_runtime_dir {
        runtime_dir.to_path_buf()
    } else if let Some(runtime_dir) = get_runtime_dir() {
        runtime_dir
    } else {
        // Create the runtime directory if not already present
        let runtime_dir = if is_portable_mode() {
            get_portable_runtime_path().expect("unable to obtain runtime directory path")
        } else {
            get_default_runtime_path()
        };
        info!("creating runtime directory in {}", runtime_dir.display());
        create_dir_all(&runtime_dir).expect("unable to create runtime directory");
        runtime_dir
    };

View on GitHub (pinned to e6c3736675)

Solutions

  1. Check whether the target path (dirs::config_dir()/espanso) exists as a file and remove/rename it so it can be a directory.
  2. Fix permissions on the parent directory (chown/chmod) so create_dir_all can succeed; ensure $HOME points to a writable directory.
  3. Pass --config-dir to force espanso to use an existing writable directory instead of the default location.
  4. Free disk space or remount the filesystem read-write if the create failed due to ENOSPC/EROFS.

Example fix

// shell instead of code
// before
$ espanso start
thread 'main' panicked: unable to create config directory
// after
$ ls -la ~/.config/espanso   # found a plain file
$ mv ~/.config/espanso ~/.config/espanso.bak
$ espanso start  # now creates the directory successfully
Defensive patterns

Strategy: validation

Validate before calling

let config_dir = dirs::config_dir().map(|d| d.join("espanso"));
if let Some(dir) = &config_dir {
    if let Ok(md) = std::fs::metadata(dir) {
        if !md.is_dir() { eprintln!("{} exists but is not a directory", dir.display()); }
    } else if let Some(parent) = dir.parent() {
        let writable = std::fs::metadata(parent).map(|m| !m.permissions().readonly()).unwrap_or(false);
        if !writable { eprintln!("parent {} not writable", parent.display()); }
    }
}

Type guard

fn is_creatable_dir(path: &std::path::Path) -> bool {
    !path.exists() || path.is_dir()
}

Try / catch

match std::fs::create_dir_all(&config_dir) {
    Ok(()) => {},
    Err(e) => { eprintln!("config dir {}: {}", config_dir.display(), e); std::process::exit(1); }
}

Prevention

When it happens

Trigger: Calling resolve_paths (or any espanso CLI entrypoint that calls it) when: the parent of the default config path exists but is not writable (permission denied), the path exists as a regular FILE instead of a directory, the disk is full or read-only, or an intermediate directory (e.g. $HOME/.config) is unwritable.

Common situations: Running espanso as a different user than the one owning the config path (e.g. under systemd/sudo with a wrong $HOME), HOME unset or mispointed so the default path lands somewhere unwritable, a stale file named like the config dir left by a botched migration, restoring backups that changed ownership, running from a read-only root filesystem or full disk.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


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