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

  1. Set XDG_DATA_HOME (or HOME) in the launching environment, e.g. Environment=HOME=%h in the systemd unit
  2. If running in a container/sandbox, mount or export a writable data dir before calling register
  3. Only call register() in user-facing sessions (desktop entry) where XDG dirs are guaranteed
  4. 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

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/0d1d5f4e0cf5c5f7. Report an issue: GitHub.