Hmbown/CodeWhale · error

skill ' ' not found in Codewhale-owned project/global roots

Error message

skill '{name}' not found in Codewhale-owned project/global roots

What it means

Name-based mutations resolve against Codewhale-owned project and global roots only. If no owned-root skill matches the (canonicalized) name — and no compatible-external copy exists either — the resolver reports the skill as not found in the owned roots and the mutation fails.

Solutions

  1. Run a skill list/catalog query to see the exact names of owned skills and use the canonical name
  2. Check both project and global roots; pass explicit --project or --global scope if the name exists in one of them
  3. Re-import or install the skill into an owned root if it is genuinely missing
  4. Normalize the name (canonical form) before passing it to the mutation API

Example fix

// before
remove_by_name("my_skill")?; // canonical name is "my-skill"
// after
remove_by_name("my-skill")?; // use catalog-canonical name
Defensive patterns

Strategy: validation

Validate before calling

fn find_owned_skill(catalog: &SkillCatalog, name: &str) -> Option<&Skill> {
    let canonical = canonicalize_name(name);
    catalog.owned_skills().iter()
        .find(|s| s.id.canonical_name == canonical)
}

Try / catch

match find_owned_skill(&catalog, name) {
    None => eprintln!("no owned skill named {name}; list skills to see canonical names"),
    Some(_) => mutation::execute(request, &ctx)?,
}

Prevention

When it happens

Trigger: execute/execute_sync with a name-based remove/update/trust request where the catalog's owned project+global skills contain no entry whose canonical name matches; the skill exists under a different name, in an incompatible/unregistered directory, or the wrong scope was implied.

Common situations: Typo in the skill name or a name-normalization mismatch (e.g. underscores vs hyphens); the skill was installed into a directory the catalog does not scan; the skill was uninstalled earlier; querying global when the skill lives in project (or vice versa) without an explicit scope when a dual-scope disambiguation is required.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

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

        [] => {
            // If the name only exists externally, tell the user to import.
            let compatible = scan_with_configured(
                ctx.workspace,
                ctx.home,
                ctx.configured_skills_dir,
                SkillAuditMode::Compatible,
                None,
            );
            if compatible.skills.iter().any(|s| {
                s.id.canonical_name == canonical
                    && 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(

View on GitHub (pinned to 73e0f67d83)