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.

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.

Appendix: 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)