libnyanpasu/clash-nyanpasu · error

refusing to remove directory as a profile resource: {}

Error message

refusing to remove directory as a profile resource: {}

What it means

`remove_nofollow` deletes a managed profile resource using no-follow semantics: it inspects with `symlink_metadata` and calls `remove_file`. If the path is a real directory (not a symlink/reparse point), `remove_file` would fail confusingly (or on some platforms behave unexpectedly), so the code refuses up front with this error. It protects against recursively significant structures being destroyed as if they were single files.

Source

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

        }
        if journal
            .managed_path
            .as_path()
            .components()
            .any(|component| is_materialization_root_name(component.as_os_str()))
        {
            bail!("materialization journal targets reserved private storage");
        }
        if journal.hash.len() != 64 || !journal.hash.bytes().all(|byte| byte.is_ascii_hexdigit()) {
            bail!("materialization journal hash is invalid");
        }
        Ok(journal)
    }

    fn remove_nofollow(path: &Path) -> anyhow::Result<()> {
        match std::fs::symlink_metadata(path) {
            Ok(metadata) if metadata.is_dir() && !is_symlink_or_reparse(&metadata) => {
                bail!(
                    "refusing to remove directory as a profile resource: {}",
                    path.display()
                )
            }
            Ok(_) => {
                std::fs::remove_file(path)
                    .with_context(|| format!("remove profile resource {}", path.display()))?;
                sync_directory(path.parent().expect("profile resource has parent"))
            }
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
            Err(error) => {
                Err(error).with_context(|| format!("inspect profile resource {}", path.display()))
            }
        }
    }

    fn remove_private_regular(path: &Path) -> anyhow::Result<()> {
        match std::fs::symlink_metadata(path) {

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Inspect the reported path; if the directory contents are not needed, remove it manually (`rm -r` / `Remove-Item -Recurse`) so the app can recreate it as a file/symlink
  2. If the directory holds user data, move it elsewhere first, then delete the empty directory
  3. Re-run the profile materialization after clearing the path
  4. Do not place directories at paths the app manages as files

Example fix

// before: managed path is a directory
~/.config/nyanpasu/profiles/config.yaml/   (directory)
// after
mv ~/.config/nyanpasu/profiles/config.yaml ~/config.yaml.backup
rmdir ~/.config/nyanpasu/profiles/config.yaml
Defensive patterns

Strategy: validation

Validate before calling

fn removable_as_file(path: &std::path::Path) -> bool {
    match std::fs::symlink_metadata(path) {
        Ok(md) => !(md.is_dir() && !md.file_type().is_symlink()),
        Err(e) => e.kind() == std::io::ErrorKind::NotFound,
    }
}
// check before invoking operations that remove managed resources

Type guard

fn is_real_dir(md: &std::fs::Metadata) -> bool {
    md.is_dir() && !md.file_type().is_symlink()
}

Try / catch

if let Err(e) = remove_managed(&path) {
    if e.to_string().contains("refusing to remove directory") {
        // preserve contents, then clear the directory manually before retrying
        std::fs::read_dir(&path)?.for_each(...); // backup, then remove_dir_all
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Removing a profile resource whose path (e.g. a managed profile file or backup link path) is an actual directory on disk. Happens when a user created a directory where the app expects a file/symlink, or a previous materialization left a directory in place of the managed item.

Common situations: User creates a folder with the same name as an expected profile file (e.g. `config.yaml/`); dotfile managers replacing files with directories; an interrupted operation left a directory at a managed path.

Related errors


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