libnyanpasu/clash-nyanpasu · error

profile directory is a symlink, reparse point, or non-direct

Error message

profile directory is a symlink, reparse point, or non-directory: {}

What it means

ensure_real_directory validates that a directory on the profile parent chain is a genuine directory: if the filesystem metadata reports a symlink/reparse point, or the entry is not a directory, the operation is aborted. The app refuses to traverse or create through link-like entries in the profiles directory tree to keep its private storage from being redirected.

Source

Thrown at backend/tauri/src/service/profile_file.rs:394

impl CleanupPhase {
    fn directory(self) -> &'static str {
        match self {
            Self::Pending => "cleanup/pending",
            Self::Ready => "cleanup/ready",
        }
    }
}

#[derive(Debug)]
enum StoredResource {
    File { path: PathBuf },
    Symlink { target: ExternalProfilePath },
}

fn ensure_real_directory(path: &Path, metadata: &std::fs::Metadata) -> anyhow::Result<()> {
    if is_symlink_or_reparse(metadata) || !metadata.is_dir() {
        bail!(
            "profile directory is a symlink, reparse point, or non-directory: {}",
            path.display()
        );
    }
    Ok(())
}

fn ensure_real_directory_tree(path: &Path) -> anyhow::Result<()> {
    match std::fs::symlink_metadata(path) {
        Ok(metadata) => ensure_real_directory(path, &metadata),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            let parent = path
                .parent()
                .context("directory creation path has no parent")?;
            ensure_real_directory_tree(parent)?;
            let created = match std::fs::create_dir(path) {
                Ok(()) => true,
                Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => false,

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Replace the symlink/junction with a real directory and move any content into it, then retry
  2. Delete any plain file occupying the required directory name so the app can create the directory itself
  3. Re-run backup/restore or sync excluding the app's profiles directory so links are not recreated
  4. Check entries beforehand with symlink_metadata (is_symlink/file_attributes reparse bit) and fail fast with your own message

Example fix

// before (unix)
ln -s /elsewhere/profiles "$root/profiles/sub"
// after (unix)
rm "$root/profiles/sub" && mkdir "$root/profiles/sub" && cp -r /elsewhere/profiles/. "$root/profiles/sub/"
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
pub fn is_real_directory(path: &Path) -> bool {
    match std::fs::symlink_metadata(path) {
        Ok(md) => !is_symlink_or_reparse(&md) && md.is_dir(),
        Err(_) => false,
    }
}
// unix: md.file_type().is_symlink(); windows: (md.file_attributes() & 0x400) != 0 (FILE_ATTRIBUTE_REPARSE_POINT)

Try / catch

match client.get_profiles().await {
    Err(e) if e.to_string().contains("symlink, reparse point, or non-directory") => {
        eprintln!("replace links/files in the profiles directory with real directories, then retry");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Any code path that walks or builds the profiles directory chain (validate_existing_parent_chain, ensure_profiles_root, ensure_directory_chain, ensure_real_directory_tree) encountering an entry whose metadata has symlink/reparse attributes or is a plain file where a directory is required — e.g. 'profiles/sub' is an NTFS junction, an alias, or a file named 'sub'.

Common situations: Cloud-sync clients replacing profile folders with reparse points; a user creating a file where the app expects a directory (e.g. a file literally named like the profiles root or a group folder); restored backups containing symlinks; macOS/Linux dotfile managers (stow, symlinking configs) linking profile directories.

Related errors


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