Morganamilo/paru · error · anyhow::Error

failed to find config directory

Error message

failed to find config directory

What it means

paru's Config::new() resolves the user's XDG config directory via the `dirs` crate (dirs::config_dir()) and appends "paru" to it. If no config directory can be determined (e.g. $XDG_CONFIG_HOME unset and no home directory resolvable), it returns this anyhow error instead of constructing a Config.

Solutions

  1. Set HOME to the user's home directory before invoking paru.
  2. Set XDG_CONFIG_HOME explicitly (e.g. XDG_CONFIG_HOME=$HOME/.config).
  3. Check that the invoking user exists in /etc/passwd with a valid home directory.
  4. If embedding paru as a library, ensure dirs::config_dir() prerequisites are met before calling Config::new().

Example fix

// before (container CMD)
CMD ["paru", "-Syu"]
// after
CMD ["sh", "-c", "export HOME=${HOME:-/root} XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} && exec paru -Syu"]
Defensive patterns

Strategy: validation

Validate before calling

if std::env::var("HOME").is_err() && std::env::var("XDG_CONFIG_HOME").is_err() {
    std::env::set_var("XDG_CONFIG_HOME", "/root/.config"); // or fail fast with a clear message
}
assert!(dirs::config_dir().is_some(), "no config dir resolvable");

Type guard

fn has_config_dir() -> bool { dirs::config_dir().is_some() }

Prevention

When it happens

Trigger: Calling Config::new() (directly or via paru startup) when dirs::config_dir() returns None — typically because HOME is unset, the passwd entry lacks a home dir, or running in a stripped environment (cron, systemd service, containers).

Common situations: Running paru from a systemd unit, CI job, or Docker container without HOME/XDG_CONFIG_HOME set; running as a user with a malformed /etc/passwd entry; musl/minimal chroot environments.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Morganamilo/paru@9ac3578807 (2026-09-12). Data as JSON: /api/errors/ea0fda79dd143293. Report an issue: GitHub.

Appendix: source

Thrown at src/config.rs:584

                    self.pkgbuild_repos.add_repo(section.to_string());
                }
                Ok(())
            }
            CallbackKind::Directive(_, key, value) => self.parse_directive(key, value),
        };

        let filename = cb.filename.unwrap_or("paru.conf");
        err.map_err(|e| anyhow!("{}:{}: {}", filename, cb.line_number, e))
    }
}

impl Config {
    pub fn new() -> Result<Self> {
        let cache =
            dirs::cache_dir().ok_or_else(|| anyhow!(tr!("failed to find cache directory")))?;
        let cache = cache.join("paru");
        let config =
            dirs::config_dir().ok_or_else(|| anyhow!(tr!("failed to find config directory")))?;
        let config = config.join("paru");
        let state = dirs::state_dir()
            .or_else(dirs::cache_dir)
            .ok_or_else(|| anyhow!(tr!("failed to find state directory")))?;
        let state = state.join("paru");

        let build_dir = cache.join("clone");
        let old_old_devel_path = cache.join("devel.json");
        let old_devel_path = state.join("devel.json");
        let devel_path = state.join("devel.toml");
        let config_path = config.join("paru.conf");

        let old = if old_devel_path.exists() {
            Some(&old_devel_path)
        } else if old_old_devel_path.exists() {
            Some(&old_old_devel_path)
        } else {
            None

View on GitHub (pinned to 9ac3578807)