libnyanpasu/clash-nyanpasu · error

existing non-symlink file at {}, refusing to replace

Error message

existing non-symlink file at {}, refusing to replace

What it means

ProfileFileService::ensure_symlink refuses to replace an existing regular file (or directory) at a managed path with a symlink. This is a deliberate safety fence against destroying user data: the service will only swap out an existing symlink, never delete a real file that it did not create. The caller must explicitly remove the non-symlink entry before requesting a symlink there.

Source

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

            Err(e) => Err(e).with_context(|| format!("inspect profile file {}", full.display())),
        }
    }

    fn ensure_symlink(
        &self,
        path: &ManagedProfilePath,
        target: &ExternalProfilePath,
    ) -> anyhow::Result<()> {
        let full = self.resolve(path)?;
        self.ensure_managed_parent(&full)?;
        match std::fs::symlink_metadata(&full) {
            Ok(meta) if meta.file_type().is_symlink() => {
                if symlink_points_to(&full, target.as_path())? {
                    return Ok(());
                }
                std::fs::remove_file(&full)?;
            }
            Ok(_) => bail!(
                "existing non-symlink file at {}, refusing to replace",
                full.display()
            ),
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
            Err(e) => Err(e).with_context(|| format!("inspect profile file {}", full.display()))?,
        }
        #[cfg(windows)]
        std::os::windows::fs::symlink_file(target.as_path(), &full)?;
        #[cfg(unix)]
        std::os::unix::fs::symlink(target.as_path(), &full)?;
        Ok(())
    }
}

impl ProfileMaterializationPort for ProfileFileService {
    fn prepare_state_first(
        &self,
        path: &ManagedProfilePath,

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Remove the existing regular file at the reported path (after backing up its contents) and retry ensure_symlink.
  2. If the file's contents should be preserved, move them to the intended external target path first, then remove the file and call ensure_symlink.
  3. Check for sync/backup software that materializes symlinks into real files and exclude the app data directory.
  4. Use the service's remove(path) API instead of writing through the path, so the managed entry is deleted before linking.

Example fix

// before
// profile file was materialized as a regular file; this now fails
service.ensure_symlink(&path, &external)?;

// after
let full = service.resolve(&path)?;
if std::fs::symlink_metadata(&full).map(|m| !m.file_type().is_symlink()).unwrap_or(false) {
    std::fs::rename(&full, backup_path)?; // preserve contents
}
service.ensure_symlink(&path, &external)?;
Defensive patterns

Strategy: validation

Validate before calling

let full = service.resolve(&path)?;
match std::fs::symlink_metadata(&full) {
    Ok(m) if m.file_type().is_symlink() => Ok(()),
    Ok(_) => Err(anyhow!("{} is a regular file; move it to the external target first", full.display())),
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
    Err(e) => Err(e.into()),
}

Type guard

fn is_symlink(p: &std::path::Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.file_type().is_symlink()).unwrap_or(false)
}

Prevention

When it happens

Trigger: Calling ProfileFileService::ensure_symlink(path, target) when `path` already exists as a regular file (symlink_metadata succeeds and the entry is not a symlink). Occurs when a profile item migrates from an inline/file-backed layout to an external-link layout while a real file still occupies the managed path, or a stale non-symlink file was left behind by a previous version or manual copy.

Common situations: Upgrading from an older app version that stored the profile as a regular file while the new version links to an external target; the user (or a sync tool like Dropbox/OneDrive) dropped a real file where the app expects to place its symlink; a restore/backup tool replaced symlinks with real file copies.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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