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
- Pass the canonical skills root: anchor/.codewhale/skills
- Fix configuration so the skills dir is derived from the anchor rather than set independently
- 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
- Always derive the skills dir from the anchor, never configure it independently
- Do not append extra segments to the skills root
- Use the library's own resolver helpers instead of hand-built paths
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
- audited skill path does not match owned package
- InvalidInput
- owned skill root does not exist
- 127
- A pinned task provider requires an explicit model
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)