zeroclaw-labs/zeroclaw · error · anyhow::Error

Destination skill already exists: {}

Error message

Destination skill already exists: {}

What it means

install_local_skill_source derives the install name from the source directory's file_name and refuses to proceed when skills/<name> already exists — it never overwrites an installed skill. Any local source whose folder name matches an installed skill collides, regardless of where it came from.

Source

Thrown at crates/zeroclaw-runtime/src/skills/mod.rs:2099

    skills_path: &Path,
    allow_scripts: bool,
) -> Result<(PathBuf, usize)> {
    let source_path = PathBuf::from(source);
    if !source_path.exists() {
        anyhow::bail!("Source path does not exist: {source}");
    }

    let source_path = source_path
        .canonicalize()
        .with_context(|| format!("failed to canonicalize source path {source}"))?;
    let _ = enforce_skill_security_audit(&source_path, allow_scripts)?;

    let name = source_path
        .file_name()
        .context("Source path must include a directory name")?;
    let dest = skills_path.join(name);
    if dest.exists() {
        anyhow::bail!(
            "Destination skill already exists: {}",
            dest.display().to_string()
        );
    }

    if let Err(err) = copy_dir_recursive_secure(&source_path, &dest) {
        let _ = std::fs::remove_dir_all(&dest);
        return Err(err);
    }

    match enforce_skill_security_audit(&dest, allow_scripts) {
        Ok(report) => Ok((dest, report.files_scanned)),
        Err(err) => {
            let _ = std::fs::remove_dir_all(&dest);
            Err(err)
        }
    }
}

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Remove the installed copy first (uninstall the skill or delete skills/<name>), then install again
  2. If you need both versions, rename the source directory so the installed names differ
  3. Check for leftover partial directories from earlier failed installs

Example fix

# before
zeroclaw skills install ~/src/my-skill   # Destination skill already exists: .../skills/my-skill

# after
zeroclaw skills uninstall my-skill && zeroclaw skills install ~/src/my-skill
Defensive patterns

Strategy: validation

Validate before calling

let name = std::path::Path::new(source)
    .file_name().and_then(|n| n.to_str())
    .context("source must end in a directory name")?;
if skills_path.join(name).exists() {
    anyhow::bail!("skill '{name}' already installed; uninstall first");
}
install_local_skill_source(source, &skills_path, allow_scripts)?;

Type guard

fn install_would_collide(source: &str, skills_path: &std::path::Path) -> Option<std::path::PathBuf> {
    let name = std::path::Path::new(source).file_name()?;
    let dest = skills_path.join(name);
    dest.exists().then_some(dest)
}

Prevention

When it happens

Trigger: Reinstalling a skill to upgrade it; installing a different skill whose directory has the same name as one already present; leftover directory from a previous manual copy.

Common situations: Upgrading by installing a fresh checkout; two skills from different authors both named e.g. code-review.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/838a72a179d6a661. Report an issue: GitHub.