Hmbown/CodeWhale · error
audited skill path does not match owned package
Error message
audited skill path {} does not match owned package {} What it means
After validating that the skill's audited root is intact, the library computes the exact on-disk path the owned skill package should occupy (skills_dir + package name) and requires the mutation target path to equal it exactly. A mismatch means the caller is trying to mutate a path that is not the audited skill's package — possibly a different skill, a symlink, or a crafted path.
Solutions
- Derive the path from the AuditedSkill via the same catalog helpers the library uses (skills_dir joined with on_disk_package_name) instead of constructing it manually
- Check for symlinks in the passed path and resolve/reject them; use the real path of the package
- Verify the skill's on-disk package name matches the skill ID's canonical name; re-import if the directory was renamed
- List owned skills through the catalog and use the returned path directly
Example fix
// before
let path = skills_dir.join("My Skill"); // manual, wrong casing
mutation::update_skill(&audited, &path, &ctx)?;
// after
let path = validate_owned_skill_path(&ctx, &audited, /* use derived */ &skills_dir.join(on_disk_package_name(&audited.id))?)?;
mutation::update_skill(&audited, &path, &ctx)?; Defensive patterns
Strategy: validation
Validate before calling
fn path_matches_owned(skills_dir: &Path, skill: &AuditedSkill, path: &Path) -> Result<bool> {
Ok(path == skills_dir.join(on_disk_package_name(&skill.id)?))
} Try / catch
if let Err(e) = mutation::update_skill(&audited, &path, &ctx) {
if e.to_string().contains("does not match owned package") {
eprintln!("target path is not the audited package; use the catalog-derived path");
}
return Err(e.into());
} Prevention
- Never hand-construct skill package paths; derive them from the skill ID via the catalog helpers
- Reject or resolve symlinks before passing a path to a mutation API
- Use canonical names, not display names, when building package paths
When it happens
Trigger: Passing a path argument to update_skill/remove_skill/trust_skill that differs from skills_dir.join(on_disk_package_name(skill.id)) — e.g. a path with a different package name, a symlinked alias to the package, a path under a different root, or a path constructed manually instead of derived from the skill ID.
Common situations: Tooling builds the path from the skill's display name rather than its canonical package name (normalization mismatch); a symlink shortcut points at the package; the caller operates on a copy of the skill in a backup directory.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- bundle path escapes the config directory via a symlink…
- owned skill root escapes anchor
- refusing to copy symlink
- refusing to import unsafe external skill package
- refusing to mutate symlinked Codewhale skills path component
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a3ffb7eac5770f68.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/skills/mutation.rs:339
skills_dir.display()
);
}
Ok(true)
}
fn validate_owned_skill_path(
ctx: &MutationContext<'_>,
skill: &AuditedSkill,
path: &Path,
) -> Result<PathBuf> {
if skill.id.root_id != skill.root.id {
bail!("audited skill root identity changed; refusing mutation");
}
let skills_dir = validate_owned_root_descriptor(ctx, &skill.root)?;
let package_name = on_disk_package_name(&skill.id)?;
let expected = skills_dir.join(package_name);
if path != expected {
bail!(
"audited skill path {} does not match owned package {}",
path.display(),
expected.display()
);
}
validate_owned_child(&skills_dir, path, true)?;
Ok(skills_dir)
}
/// Resolve the on-disk CodeWhale-owned skills directory for a target scope.
pub fn resolve_owned_target(
workspace: &Path,
home: Option<&Path>,
target: SkillTargetScope,
) -> Result<PathBuf> {
let catalog = SkillRootCatalog::build(workspace, home, None);
let kind = match target {
SkillTargetScope::Project => SkillRootKind::CodeWhaleProject,View on GitHub (pinned to 73e0f67d83)