libnyanpasu/clash-nyanpasu · error

runtime candidate path is not a private directory: {path}

Error message

runtime candidate path is not a private directory: {path}

What it means

Raised by prepare_private_dir (backend/tauri/src/client/runtime.rs:275) after create_dir_all when the final symlink_metadata shows the path is either still a symlink/reparse point or is not a directory. The runtime store requires a real, private directory to hold exclusive-create candidate files; anything else is rejected to protect file privacy guarantees (0600 files in a 0700 dir).

Source

Thrown at backend/tauri/src/client/runtime.rs:275

impl Drop for CandidateFile {
    fn drop(&mut self) {
        if !self.cleaned {
            let _ = std::fs::remove_file(&self.path);
        }
    }
}

async fn prepare_private_dir(path: &Utf8Path) -> anyhow::Result<()> {
    if let Ok(metadata) = tokio::fs::symlink_metadata(path).await
        && is_symlink_or_reparse(&metadata)
    {
        anyhow::bail!("runtime candidate directory is a symlink or reparse point: {path}");
    }
    tokio::fs::create_dir_all(path).await?;
    let metadata = tokio::fs::symlink_metadata(path).await?;
    if is_symlink_or_reparse(&metadata) || !metadata.is_dir() {
        anyhow::bail!("runtime candidate path is not a private directory: {path}");
    }
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).await?;
    }
    Ok(())
}

#[cfg(unix)]
fn is_symlink_or_reparse(metadata: &std::fs::Metadata) -> bool {
    metadata.file_type().is_symlink()
}

#[cfg(windows)]
fn is_symlink_or_reparse(metadata: &std::fs::Metadata) -> bool {
    use std::os::windows::fs::MetadataExt;
    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Check what exists at the path (ls -la / dir) and delete the offending file or symlink so a real directory can be created.
  2. Re-run after removing the file at that path; create_dir_all will then create a proper directory.
  3. Resolve the process racing on this path (another app instance or sync tool) before retrying.
  4. Verify app data directory configuration points at a directory, not a file path.

Example fix

// before: a regular file occupies the candidate dir path
// after: remove the file and create the directory
std::fs::remove_file("/app/data/candidates")?;
std::fs::create_dir_all("/app/data/candidates")?;
Defensive patterns

Strategy: validation

Validate before calling

let md = std::fs::symlink_metadata(&candidate_path)?;
if !md.is_dir() || md.file_type().is_symlink() {
    // clear the way so create_dir_all can build a real directory
    std::fs::remove_file(&candidate_path)?;
    std::fs::create_dir_all(&candidate_path)?;
}

Type guard

fn is_real_directory(path: &Utf8Path) -> bool {
    std::fs::symlink_metadata(path.as_std_path())
        .map(|md| md.is_dir() && !md.file_type().is_symlink())
        .unwrap_or(false)
}

Try / catch

match store.create_candidate(&bytes).await {
    Ok(c) => c,
    Err(e) if e.to_string().contains("not a private directory") => {
        std::fs::remove_file(path.as_std_path()).ok();
        std::fs::create_dir_all(path.as_std_path())?;
        store.create_candidate(&bytes).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: create_candidate_with_names, cleanup_stale_candidates, or candidate_collision_retries_with_exclusive_create calls prepare_private_dir; after create_dir_all, the path exists but is a regular file, a symlink, or another non-directory node, or was swapped between the create and the metadata check.

Common situations: A file named like the candidate dir exists at that path so create_dir_all fails silently or the check runs against the file; a race where another process replaced the directory with a symlink; corrupted install where the data dir path collides with a file.

Related errors


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