Hmbown/CodeWhale · error

audited owned root does not match mutation target

Error message

audited owned root {} does not match mutation target {}

What it means

validate_owned_root_descriptor re-derives the expected owned skills root for the descriptor's scope (project or global) and compares it with the path recorded in the audited root descriptor. A mismatch means the audit snapshot is stale or points at a different location than the mutation would resolve, so the mutation is refused to avoid writing outside the audited location.

Solutions

  1. Re-run skill discovery/audit in the current workspace+home so the root descriptor path matches the resolved target, then retry the mutation.
  2. Check that the workspace path and HOME are the same as when the skill was audited; correct the invocation's workspace/home context.
  3. If config changed the skills root, reload config and rebuild the audited skill list before mutating.

Example fix

// before
let audited = load_cached_audit(old_workspace);
update_skill(&ctx, &audited.skill)?; // stale path
// after
let audited = rediscover_and_audit(&ctx.workspace, ctx.home)?;
update_skill(&ctx, &audited.skill)?;
Defensive patterns

Strategy: validation

Validate before calling

fn audit_is_current(ctx: &MutationContext, root: &SkillRootDescriptor) -> bool {
    match target_scope_for_root(root) {
        Ok(scope) => resolve_owned_target(ctx.workspace, ctx.home, scope)
            .map(|expected| expected == root.path)
            .unwrap_or(false),
        Err(_) => false,
    }
}

Try / catch

// match on the error to distinguish stale-audit from other failures
match update_skill(&ctx, &skill) {
    Err(e) if e.to_string().contains("does not match mutation target") => {
        let fresh = rediscover_and_audit(&ctx.workspace, ctx.home)?;
        update_skill(&ctx, &fresh.skill)?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling validate_owned_skill_path or update_skill where root.path != resolve_owned_target(workspace, home, scope): e.g. the workspace directory changed between audit and mutation, HOME moved, the project was relocated, or a stale AuditedSkill from an old session is reused.

Common situations: Renaming/moving the repo or changing the workspace root after listing skills; running with a different $HOME; stale cached audit results after a config change; passing a global-scope skill audit into a mutation with a different home.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

fn target_scope_for_root(root: &SkillRootDescriptor) -> Result<SkillTargetScope> {
    if !root.is_writable_owned() {
        bail!("refusing to mutate non-owned root {}", root.path.display());
    }
    match root.kind {
        SkillRootKind::CodeWhaleProject => Ok(SkillTargetScope::Project),
        SkillRootKind::CodeWhaleGlobal => Ok(SkillTargetScope::Global),
        _ => bail!("refusing to mutate non-owned root {}", root.path.display()),
    }
}

fn validate_owned_root_descriptor(
    ctx: &MutationContext<'_>,
    root: &SkillRootDescriptor,
) -> Result<PathBuf> {
    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()

View on GitHub (pinned to 73e0f67d83)