Hmbown/CodeWhale · error
refusing to mutate path
Error message
refusing to mutate path {} outside direct owned root {} What it means
validate_owned_child requires the path being mutated to be a direct child of the already-validated owned skills root (child.parent() == skills_dir). Nested paths or sibling paths are rejected so mutations can only touch a skill package directory one level under the root, blocking writes elsewhere in the tree.
Solutions
- Pass the exact direct child path skills_dir.join(package_name); strip any nested components from the skill/package name.
- Sanitize or reject package names containing '/', '\\', or '..' before calling the mutation API.
- Verify the skills_dir you pass is the actual validated root, not a subdirectory of it.
Example fix
// before
let target = skills_dir.join("vendor").join(name);
install_remote(&ctx, &target)?;
// after
let name = sanitize_package_name(name)?; // rejects separators
let target = skills_dir.join(name);
install_remote(&ctx, &target)?; Defensive patterns
Strategy: validation
Validate before calling
fn is_direct_child(skills_dir: &std::path::Path, child: &std::path::Path) -> bool {
child.parent() == Some(skills_dir)
&& child.file_name().map(|n| !n.to_string_lossy().contains('/')).unwrap_or(false)
} Prevention
- Always construct child paths as skills_dir.join(single_component_name).
- Sanitize package/skill names: reject '/', '\\', '..', and empty components.
- Never accept user-supplied full paths for mutation APIs; accept names only.
When it happens
Trigger: Calling validate_owned_skill_path, install_remote, import_external, or update_skill with a child path that is not an immediate child of the validated skills_dir — e.g. a nested subdirectory, a relative path resolving outside the root, or a constructed path with extra components.
Common situations: A skill package name containing a path separator; hand-built paths like <root>/<vendor>/<skill> passed to install/import; symlinked or misconfigured install target; a caller joining user-controlled skill names onto the root.
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
- owned skill path escapes direct root
- audited owned root does not match mutation target
- Invalid durable task id
- only Codewhale managed skills can be removed
- only CodeWhale managed skills can be trusted
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/1854ef27b9b538f2.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/skills/mutation.rs:295
let target = target_scope_for_root(root)?;
let expected = resolve_owned_target(ctx.workspace, ctx.home, target)?;
if root.path != expected {
bail!(
"audited owned root {} does not match mutation target {}",
root.path.display(),
expected.display()
);
}
let anchor = owned_anchor(ctx.workspace, ctx.home, target)?;
validate_owned_target_chain(anchor, &expected, true)?;
Ok(expected)
}
/// Validate a real direct child of an already validated owned skills root.
/// Returns false only when a missing child is permitted.
fn validate_owned_child(skills_dir: &Path, child: &Path, require_existing: bool) -> Result<bool> {
if child.parent() != Some(skills_dir) {
bail!(
"refusing to mutate path {} outside direct owned root {}",
child.display(),
skills_dir.display()
);
}
let exists = checked_real_directory(child)?;
if !exists {
if require_existing {
bail!("owned skill path {} does not exist", child.display());
}
return Ok(false);
}
let canonical_root = fs::canonicalize(skills_dir).with_context(|| {
format!(
"failed to resolve owned skill root {}",
skills_dir.display()
)View on GitHub (pinned to 73e0f67d83)