libnyanpasu/clash-nyanpasu · error · std::io::Error (NotFound)
data directory not found.
Error message
data directory not found.
What it means
The Linux deep-link plugin register() writes a .desktop file under the XDG data dir's `applications` folder so the OS can route deep-link schemes to the app. It first resolves data_dir(); when no XDG data directory can be determined (typically $XDG_DATA_HOME unset and $HOME unavailable), it fails with this std::io::Error NotFound.
Source
Thrown at backend/tauri-plugin-deep-link/src/linux.rs:16
use std::{
fs::{create_dir_all, remove_file, File},
io::{Error, ErrorKind, Read, Result, Write},
os::unix::net::{UnixListener, UnixStream},
process::Command,
};
use dirs::data_dir;
use crate::ID;
pub fn register<F: FnMut(String) + Send + 'static>(schemes: &[&str], handler: F) -> Result<()> {
listen(handler)?;
let mut target = data_dir()
.ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))?
.join("applications");
create_dir_all(&target)?;
let exe = tauri_utils::platform::current_exe()?;
let file_name = format!(
"{}-handler.desktop",
exe.file_name()
.ok_or_else(|| Error::new(
ErrorKind::NotFound,
"Couldn't get file name of curent executable.",
))?
.to_string_lossy()
);
target.push(&file_name);
View on GitHub (pinned to f7dbce2997)
Solutions
- Set XDG_DATA_HOME (or HOME) in the launching environment, e.g. Environment=HOME=%h in the systemd unit
- If running in a container/sandbox, mount or export a writable data dir before calling register
- Only call register() in user-facing sessions (desktop entry) where XDG dirs are guaranteed
- Wrap register() and degrade gracefully when data_dir() is None — deep-link registration is optional
Example fix
// before
let mut target = data_dir()
.ok_or_else(|| Error::new(ErrorKind::NotFound, "data directory not found."))?
.join("applications");
// after
let Some(dir) = data_dir() else {
log::warn!("XDG data dir unavailable; skipping deep-link registration");
return Ok(());
};
let mut target = dir.join("applications"); Defensive patterns
Strategy: validation
Validate before calling
// before registering deep links on Linux
let has_data_dir = std::env::var("XDG_DATA_HOME").map(|v| !v.is_empty())
.unwrap_or(false)
|| std::env::var("HOME").map(|v| !v.is_empty()).unwrap_or(false);
if !has_data_dir {
log::warn!("XDG_DATA_HOME/HOME unset; skipping deep-link registration");
return;
} Type guard
fn xdg_data_dir_available() -> bool {
dirs::data_dir().is_some()
} Try / catch
match deep_link::register(&schemes, handler) {
Err(e) if e.to_string().contains("data directory not found") => {
log::warn!("no XDG data dir; deep links unregistered: {e}");
}
Err(e) => return Err(e.into()),
Ok(()) => {}
} Prevention
- Set XDG_DATA_HOME or HOME in service/daemon/container launch environments
- Register deep links only inside real desktop sessions
- Make deep-link registration optional with graceful degradation
When it happens
Trigger: Calling register() on Linux when both XDG_DATA_HOME and HOME are unset/empty — e.g. running the app from a systemd unit, cron, Docker container, or SSH session without a proper environment.
Common situations: App launched by a daemon/systemd service lacking the user environment; Flatpak/Snap sandbox restrictions; containers running as root with HOME unset; headless servers registering deep links.
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
- Couldn't get file name of curent executable.
- Couldn't get file name of current executable.
- Called register() before prepare()
- listen() called before prepare()
- Can't create listener
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/0d1d5f4e0cf5c5f7.
Report an issue: GitHub.