libnyanpasu/clash-nyanpasu · error

ready symlink target mismatch

Error message

ready symlink target mismatch

What it means

During create_ready_link, the staged 'ready' symlink already exists and points to a different path than the ExternalProfilePath supplied for this materialization operation. The function is idempotent: an existing ready symlink with the matching target is accepted, but any divergence means stale or corrupted staging state from another operation, so it aborts rather than promoting the wrong file.

Source

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

            }
            let target = std::fs::read_to_string(&link_path)?;
            return Ok(Some(StoredResource::Symlink {
                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,

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Delete the stale ready symlink for this operation_id and rerun the materialization so it is recreated with the current target.
  2. Run the journal compensate/reconcile flow for this operation_id to clean up inconsistent staged artifacts.
  3. Generate a fresh operation_id via allocate_operation_id and start the materialization over.
  4. Inspect the ready symlink target manually (ls -l / fs::read_link) to confirm it belongs to a different operation before removing it.

Example fix

// before
// stale ready symlink from a failed run points at old temp file
// after
// compensate(operation_id) to clear staged artifacts, then re-run promote
let _ = client.compensate(root, operation_id).await?;
client.promote(root, operation_id).await?;
Defensive patterns

Strategy: retry

Validate before calling

if let Ok(meta) = std::fs::symlink_metadata(&ready_path) {
    if meta.file_type().is_symlink() && std::fs::read_link(&ready_path)? != expected_target {
        std::fs::remove_file(&ready_path)?; // clear stale link before retry
    }
}

Type guard

fn ready_link_matches(ready: &Path, target: &Path) -> bool {
    std::fs::symlink_metadata(ready)
        .map(|m| m.file_type().is_symlink() && std::fs::read_link(ready).map(|p| p == target).unwrap_or(false))
        .unwrap_or(false)
}

Try / catch

match create_ready_link(root, &target) {
    Err(e) if e.to_string().contains("ready symlink target mismatch") => {
        let _ = std::fs::remove_file(Self::ready_link_path(root, &op_id));
        create_ready_link(root, &target) // retry once after cleanup
    }
    r => r,
}

Prevention

When it happens

Trigger: create_ready_link called with a target path while a ready symlink for the same operation_id already exists pointing elsewhere — e.g. a retried operation whose target was regenerated with a different temp path, or an operation_id colliding with a previous run.

Common situations: Re-running a profile download/materialization after a partial failure; reusing an operation id across attempts; manual edits or cleanup tools replacing the ready symlink.

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/30997032ed007bd0. Report an issue: GitHub.