libnyanpasu/clash-nyanpasu · error

ready symlink path is occupied by an unexpected node

Error message

ready symlink path is occupied by an unexpected node

What it means

create_ready_link found a node at the ready symlink path that is not a symlink (regular file, directory, etc.). The staging protocol requires that path to be either a correct symlink or absent; anything else indicates corruption or external interference, so the operation aborts.

Source

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

                target: ExternalProfilePath::new(target)?,
            }));
        }
        Ok(None)
    }

    fn create_ready_link(
        root: &Path,
        operation_id: &str,
        target: &ExternalProfilePath,
    ) -> anyhow::Result<PathBuf> {
        let ready = Self::ready_link_path(root, operation_id);
        match std::fs::symlink_metadata(&ready) {
            Ok(metadata) if metadata.file_type().is_symlink() => {
                if std::fs::read_link(&ready)? != target.as_path() {
                    bail!("ready symlink target mismatch");
                }
            }
            Ok(_) => bail!("ready symlink path is occupied by an unexpected node"),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
                create_file_symlink(target.as_path(), &ready)
                    .with_context(|| format!("create staged symlink {}", ready.display()))?;
                sync_directory(ready.parent().expect("ready symlink has parent"))?;
            }
            Err(error) => return Err(error).context("inspect ready symlink"),
        }
        Ok(ready)
    }

    fn promote_resource(
        &self,
        root: &Path,
        operation_id: &str,
        target: &Path,
        expected_hash: &str,
    ) -> anyhow::Result<()> {
        self.ensure_managed_parent(target)?;

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Remove the foreign node at the ready path (after confirming it is not user data) and rerun the materialization.
  2. Run the journal compensate/reconcile flow to reset the operation's staged state.
  3. Check the filesystem supports symlinks (Windows developer mode / not a FAT or network share) if this recurs.
  4. Verify no other process or backup tool is writing into the staging directory.

Example fix

// before
// ready path holds a regular file left by a backup tool
// after
std::fs::remove_file(&ready_path)?; // after verifying contents
client.promote(root, operation_id).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

if let Ok(meta) = std::fs::symlink_metadata(&ready_path) {
    if !meta.file_type().is_symlink() {
        // occupied by a non-symlink node: clean up before calling
        std::fs::remove_file(&ready_path)?;
    }
}

Type guard

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

Try / catch

match create_ready_link(root, &target) {
    Err(e) if e.to_string().contains("unexpected node") => {
        let _ = std::fs::remove_file(Self::ready_link_path(root, &op_id));
        create_ready_link(root, &target)
    }
    r => r,
}

Prevention

When it happens

Trigger: A ready symlink path is occupied by a non-symlink node: a regular file or directory was created at ready_link_path(root, operation_id), e.g. by a crash leftover, another tool, or a filesystem that materialized the link as a file.

Common situations: Manual cleanup replaced the symlink with a copy; sync/backup tools dereferenced symlinks into regular files; non-symlink-supporting filesystems (some Windows configs/network mounts) writing plain files.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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