Morganamilo/paru · error · anyhow::Error

failed to find state directory

Error message

failed to find state directory

What it means

Config::new() resolves a state directory with dirs::state_dir(), falling back to dirs::cache_dir(). If neither can be determined, it fails with this error. The state dir holds runtime data like devel.toml.

Solutions

  1. Set HOME to a writable directory before running paru.
  2. Set XDG_STATE_HOME (e.g. $HOME/.local/state) or XDG_CACHE_HOME so one resolver succeeds.
  3. Verify the runtime user has a valid home directory in the user database.
  4. As a last resort in containers, create /home/user, set HOME, and run as that user.

Example fix

# before (systemd service)
[Service]
ExecStart=/usr/bin/paru -Syu
# after
[Service]
Environment=HOME=/home/build
Environment=XDG_STATE_HOME=/home/build/.local/state
ExecStart=/usr/bin/paru -Syu
Defensive patterns

Strategy: validation

Validate before calling

if dirs::state_dir().is_none() && dirs::cache_dir().is_none() {
    eprintln!("set XDG_STATE_HOME or XDG_CACHE_HOME (and HOME) before running");
    std::process::exit(1);
}

Type guard

fn has_state_dir() -> bool { dirs::state_dir().or_else(dirs::cache_dir).is_some() }

Prevention

When it happens

Trigger: Calling Config::new() when both dirs::state_dir() (XDG_STATE_HOME + home resolution) and dirs::cache_dir() (XDG_CACHE_HOME + home) return None, i.e. no resolvable user home.

Common situations: Running paru under systemd timers/services with a minimal environment; containers or chroots without HOME; XDG_STATE_HOME and XDG_CACHE_HOME both unset AND HOME unset/unreadable.

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/b143577c96a0ed23. Report an issue: GitHub.

Appendix: source

Thrown at src/config.rs:588

            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
        };

        let cache_dir = cache;
        let state_dir = state;

View on GitHub (pinned to 9ac3578807)