Hmbown/CodeWhale · error

refusing to mutate non-canonical Codewhale skills root

Error message

refusing to mutate non-canonical Codewhale skills root {}

What it means

validate_owned_target_chain requires the skills directory to be exactly <anchor>/.codewhale/skills. Any other path — even a valid-looking directory — is refused, because mutation is only permitted on the canonical CodeWhale-owned skills root derived from the anchor.

Solutions

  1. Pass the canonical skills root: anchor/.codewhale/skills
  2. Fix configuration so the skills dir is derived from the anchor rather than set independently
  3. Ensure no extra segments were appended to the skills root path

Example fix

// before
let skills_dir = home.join(".codewhale/skills/custom");
// after
let skills_dir = home.join(".codewhale").join("skills");
Defensive patterns

Strategy: validation

Validate before calling

let expected = anchor.join(".codewhale").join("skills");
assert_eq!(skills_dir, expected, "skills_dir must be the canonical <anchor>/.codewhale/skills");

Type guard

fn is_canonical_skills_root(anchor: &Path, skills_dir: &Path) -> bool {
    skills_dir == anchor.join(".codewhale").join("skills")
}

Try / catch

match install_remote(anchor, name) {
    Err(e) if e.to_string().contains("non-canonical") => eprintln!("point skills_dir at <anchor>/.codewhale/skills"),
    other => other?,
}

Prevention

When it happens

Trigger: Calling install_remote, import_external, resolve_owned_target, prepare_owned_target, or validate_owned_root_descriptor with a skills_dir that differs lexically from anchor.join(".codewhale").join("skills"), e.g. a custom or doubled path like anchor/.codewhale/skills/skills.

Common situations: Hand-edited configuration pointing at a custom skills folder; passing a nested skill directory instead of the skills root; using a relative path where the code compares against a constructed absolute path.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

            "refusing to mutate through non-directory Codewhale skills path component {}",
            path.display()
        ),
        Ok(_) => Ok(true),
        Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
        Err(err) => Err(err).with_context(|| format!("failed to inspect {}", path.display())),
    }
}

/// Validate the complete owned-root chain without following a symlink in the
/// workspace/home anchor, `.codewhale`, or `skills` component.
fn validate_owned_target_chain(
    anchor: &Path,
    skills_dir: &Path,
    require_existing: bool,
) -> Result<()> {
    let expected = anchor.join(".codewhale").join("skills");
    if skills_dir != expected {
        bail!(
            "refusing to mutate non-canonical Codewhale skills root {}",
            skills_dir.display()
        );
    }

    let anchor_exists = checked_real_directory(anchor)?;
    if !anchor_exists {
        if require_existing {
            bail!("owned skill anchor {} does not exist", anchor.display());
        }
        return Ok(());
    }

    let codewhale_dir = anchor.join(".codewhale");
    let codewhale_exists = checked_real_directory(&codewhale_dir)?;
    if !codewhale_exists {
        if require_existing {
            bail!(

View on GitHub (pinned to 73e0f67d83)