Spotifyd/spotifyd · error

Failed to determine cache directory, please specify one…

Error message

Failed to determine cache directory, please specify one manually

What it means

Spotifyd's SharedConfigValues::get_cache needs a directory to store the librespot Cache (credentials, audio, data). It first uses the explicitly configured `cache_path`, otherwise it asks the `dirs` crate's ProjectDirs for the platform cache dir (e.g. XDG_CACHE_HOME on Linux). If `cache_path` is unset AND ProjectDirs::from returns None (no HOME/XDG environment available), it bails with this error.

Solutions

  1. Set `cache_path = "/path/to/cache"` in the spotifyd config file (e.g. ~/.config/spotifyd/spotifyd.conf)
  2. Ensure the $HOME environment variable is set when launching spotifyd (e.g. add Environment=HOME=%h to the systemd unit or export HOME before starting)
  3. Set XDG_CACHE_HOME to a writable directory if HOME cannot be fixed
  4. Run spotifyd as a user that has a valid home directory rather than a system/no-login user

Example fix

// before (systemd unit, HOME stripped)
[Service]
ExecStart = /usr/bin/spotifyd --no-daemon

// after
[Service]
Environment = HOME=%h
ExecStart = /usr/bin/spotifyd --no-daemon

// or in spotifyd.conf
// before
global = { }
// after
global = { cache_path = "/home/user/.cache/spotifyd" }
Defensive patterns

Strategy: validation

Validate before calling

// before starting spotifyd / calling get_cache
if std::env::var("HOME").map_or(true, |h| h.is_empty()) {
    eprintln!("HOME is unset: set cache_path in spotifyd.conf or fix the environment");
}
// or check config:
let cache_path_set = config.cache_path.is_some();

Type guard

fn has_cache_dir(cfg: &SharedConfigValues) -> bool {
    cfg.cache_path.is_some()
        || ProjectDirs::from("", "", "spotifyd")
            .map(|d| d.cache_dir().exists())
            .unwrap_or(false)
}

Try / catch

match config.get_cache(false) {
    Ok(cache) => { /* use cache */ }
    Err(e) if e.to_string().contains("cache directory") => {
        eprintln!("Set `cache_path` in your spotifyd config: {e}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_cache (directly or via spotifyd startup) when: (1) `cache_path` is not set in the spotifyd config file, and (2) ProjectDirs::from("", "", "spotifyd") returns None because $HOME (and/or $XDG_CACHE_HOME) is unset or empty — e.g. running as a systemd service without Environment/HOME, in a container, via cron, or under an environment stripped of vars.

Common situations: Running spotifyd under systemd/launchd/Docker where HOME is not set; starting spotifyd from a non-login shell or supervisord with a sanitized environment; users who never set `cache_path` in spotifyd.conf assuming a default location exists; running as a system user with no home directory.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.


AI-assisted analysis of Spotifyd/spotifyd@6af1e48d9f (2026-09-09). Data as JSON: /api/errors/2759be8e1d5648e9. Report an issue: GitHub.

Appendix: source

Thrown at src/config.rs:519

            }
        })?;

        // The call to get_merged_sections consumes the FileConfig!
        if let Some(merged_sections) = config_content.get_merged_sections() {
            self.shared_config.merge_with(merged_sections);
        }

        Ok(())
    }
}

impl SharedConfigValues {
    pub fn get_cache(&self, for_oauth: bool) -> color_eyre::Result<Cache> {
        let Some(cache_path) = self.cache_path.as_deref().map(Cow::Borrowed).or_else(|| {
            ProjectDirs::from("", "", "spotifyd")
                .map(|dirs| Cow::Owned(dirs.cache_dir().to_path_buf()))
        }) else {
            bail!("Failed to determine cache directory, please specify one manually");
        };

        if for_oauth {
            let mut creds_path = cache_path.into_owned();
            creds_path.push("oauth");
            Cache::new(Some(creds_path), None, None, None)
        } else {
            let audio_cache = !self.no_audio_cache.unwrap_or(false);

            let mut creds_path = cache_path.to_path_buf();
            creds_path.push("zeroconf");
            Cache::new(
                Some(creds_path.as_path()),
                Some(cache_path.as_ref()),
                audio_cache.then_some(cache_path.as_ref()),
                self.max_cache_size,
            )
        }

View on GitHub (pinned to 6af1e48d9f)