Hmbown/CodeWhale · error

skill ' ' exists in both project and global Codewhale…

Error message

skill '{name}' exists in both project and global Codewhale roots; specify --project or --global

What it means

When a name-based mutation matches more than one owned skill — one in the project root and one in the global root — the library refuses to guess which one to operate on and requires an explicit scope selection (--project or --global, or the scoped request variants).

Solutions

  1. Re-run the mutation with --project to target the project-root copy
  2. Or re-run with --global to target the global-root copy
  3. In code, construct the scoped request variant (project or global) instead of the name-only variant
  4. Remove one of the duplicate copies to make future unscoped commands unambiguous

Example fix

// before
cmd.args(["skills", "remove", "my-skill"]);
// after
cmd.args(["skills", "remove", "my-skill", "--project"]);
Defensive patterns

Strategy: validation

Validate before calling

fn scope_is_unambiguous(catalog: &SkillCatalog, name: &str) -> bool {
    let canonical = canonicalize_name(name);
    let hits = catalog.owned_skills().iter()
        .filter(|s| s.id.canonical_name == canonical).count();
    hits <= 1
}

Try / catch

if !scope_is_unambiguous(&catalog, name) {
    eprintln!("skill exists in both project and global roots; pass --project or --global");
} else {
    mutation::execute(request, &ctx)?;
}

Prevention

When it happens

Trigger: execute/execute_sync with an unscoped name-based request (remove/update/trust by name) while the canonical name exists in both the project and the global Codewhale skills roots, so the resolver's match list has more than one entry.

Common situations: A user installed the same skill both per-project and globally (common after promoting a project skill to global) and then runs an unscoped uninstall/update; scripts that omit the scope flag after a global install was added.

Understand the failure class

Background: "--flag is required" and "must specify" CLI errors: how missing-required-flag validation works and how to fix it — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/fe5e370b04ba301a. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/skills/mutation.rs:509

                    && s.source_kind == SkillSourceKind::CompatibleExternal
            }) {
                bail!(
                    "skill '{name}' exists only in a compatible external root; \
                     import it with /skills (refusing to write external harness directories)"
                );
            }
            bail!("skill '{name}' not found in Codewhale-owned project/global roots");
        }
        [only] => {
            let DigestState::Known(digest) = &only.digest else {
                bail!("skill '{name}' has unknown package digest; refusing mutation");
            };
            Ok(ResolvedOwnedSkill {
                id: only.id.clone(),
                digest: digest.clone(),
            })
        }
        _ => bail!(
            "skill '{name}' exists in both project and global Codewhale roots; \
             specify --project or --global"
        ),
    }
}

fn find_audited_skill(
    ctx: &MutationContext<'_>,
    skill_id: &AuditedSkillId,
) -> Result<(AuditedSkill, PathBuf)> {
    let snap = scan_with_configured(
        ctx.workspace,
        ctx.home,
        ctx.configured_skills_dir,
        SkillAuditMode::Compatible,
        None,
    );
    let skill = snap

View on GitHub (pinned to 73e0f67d83)