pbakaus/impeccable · error

Existing skills were left untouched. Re-run with --force to

Error message

Existing skills were left untouched. Re-run with --force to replace them with links.

What it means

Follow-up hint in link after link_provider_skills reported zero linked and zero already-linked with skipped > 0: matching skill folders already existed as copies, so linking was refused without --force. This line explains the existing copies were preserved and how to override.

Source

Thrown at crates/skills/src/commands.rs:492

        out(io, &format!("Source checkout: {}", source.checkout_root));
        out(io, &format!("Target harness folder(s): {}", targets.join(", ")));
        let ans = prompt.ask(io, &format!("Link impeccable skills into {} folder(s)? (Y/n) ", targets.len()))?;
        if ans == "n" || ans == "no" {
            out(io, "Aborted. Re-run with --providers=<names> to choose explicitly (e.g. --providers=claude,cursor).");
            return Err(Flow::Exit(0));
        }
    }
    let result = bundle::link_provider_skills(io, &source.bundle_root, &root, &targets, force).map_err(Flow::Throw)?;
    // Linked installs are excluded from install/update refreshes (overwriting
    // a symlink would destroy the link), so this is the only path that can
    // deliver the OpenCode command bridge to them. A copy, not a symlink: the
    // bridge is static and OpenCode scans the real commands dir. No-ops when
    // the source checkout has no built commands (#483).
    bundle::copy_provider_commands(&sys, &source.bundle_root, &root, &targets, Some(Scope::Project));
    if result.linked == 0 && result.already == 0 {
        if result.skipped > 0 {
            err(io, "Nothing was linked because matching skill folders already exist.");
            err(io, "Existing skills were left untouched. Re-run with --force to replace them with links.");
        } else {
            err(io, &format!("Nothing was linked: {} had no variants for {}.", source.bundle_root, targets.join(", ")));
        }
        return Err(Flow::Exit(1));
    }
    // Linked skill dirs resolve into the source checkout; repair the
    // launcher/binary executable bit there too (through the symlink), so a
    // checkout whose copier dropped modes still runs.
    for provider in &targets {
        let skills_dir = util::jsp::join(&[&root, provider, "skills"]);
        for skill in util::read_dir_names(&skills_dir).unwrap_or_default() {
            let dir = util::jsp::join(&[&skills_dir, &skill]);
            if util::is_dir(&dir) {
                crate::engine_binary::ensure_executable_scripts(&dir);
            }
        }
    }
    let mut parts = Vec::new();

View on GitHub (pinned to 2bc2879276)

Solutions

  1. Re-run with `--force`
  2. Delete the existing skill directories manually if you want links without force

Example fix

// before
impeccable link
// after
impeccable link --force
Defensive patterns

Strategy: validation

Validate before calling

if (existingSkills.length && !args.includes('--force')) confirm('Replace existing skills with links (--force)?');

Try / catch

try { await link(); } catch (e) { if (/--force/.test(String(e))) console.error('Existing skills untouched; rerun with --force'); }

Prevention

When it happens

Trigger: Same conditions as error 163: all matching skills exist and none were linked.

Common situations: Users surprised that a re-run of `link` did nothing because their install was copy-based.

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 pbakaus/impeccable@2bc2879276 (2026-09-08). Data as JSON: /api/errors/accf56f826a02d26. Report an issue: GitHub.