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
- Set `cache_path = "/path/to/cache"` in the spotifyd config file (e.g. ~/.config/spotifyd/spotifyd.conf)
- 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)
- Set XDG_CACHE_HOME to a writable directory if HOME cannot be fixed
- 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
- Always set an explicit cache_path in spotifyd.conf for headless deployments (systemd, Docker, cron)
- Verify HOME/XDG_CACHE_HOME are present in service unit environments
- Create the cache directory and grant write permission to the spotifyd user
- Test spotifyd startup with `systemctl --user` or a stripped env to catch missing HOME early
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)