Hmbown/CodeWhale · error

refusing to remove skill outside Codewhale-owned roots

Error message

refusing to remove skill outside Codewhale-owned roots

What it means

`remove_skill` only deletes skills inside roots marked writable/Codewhale-owned. Skills resolved from bundled, external, or otherwise non-owned roots cannot be removed through this API, protecting content Codewhale does not manage from deletion.

Solutions

  1. Only remove skills that live under a writable Codewhale-owned skills directory.
  2. For external skills, remove the owned imported copy; the external original is left in place by design.
  3. Remount/repair the skills directory so it is writable, and confirm the configured skills dir points at the real owned root.

Example fix

// before
mutation.execute_sync(Mutation::Remove { skill_id: external_skill_id, .. })?; // rejected
// after
mutation.execute_sync(Mutation::Remove { skill_id: owned_imported_id, .. })?; // owned copy only
Defensive patterns

Strategy: validation

Validate before calling

let (skill, _) = find_audited_skill(ctx, &skill_id)?;
if !skill.root.is_writable_owned() {
    return Err(anyhow!("refusing to remove: skill is outside Codewhale-owned roots"));
}

Type guard

fn removable(skill: &AuditedSkill) -> bool {
    skill.root.is_writable_owned() && skill.source_kind == SkillSourceKind::CodeWhaleManaged
}

Prevention

When it happens

Trigger: Calling the `Remove` mutation (`execute_sync` → `remove_skill`) for a skill whose `root.is_writable_owned()` is false — bundled skills, unimported external skills, or owned dirs on read-only mounts.

Common situations: Trying to delete a bundled example skill; attempting to remove an external skill that was never imported; the configured skills directory is read-only (container image, restored snapshot).

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

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

            action: SkillActionKind::Update,
            name: skill_id.canonical_name,
            scope,
            safe_target_path: safe_display_path(&path, Some(ctx.workspace), ctx.home),
            before_digest: before,
            after_digest: None,
            outcome: SkillMutationOutcome::NetworkDenied(host),
        }),
    }
}

fn remove_skill(
    skill_id: AuditedSkillId,
    expected_digest: Option<String>,
    ctx: &MutationContext<'_>,
) -> Result<SkillMutationReceipt> {
    let (skill, path) = find_audited_skill(ctx, &skill_id)?;
    if !skill.root.is_writable_owned() {
        bail!("refusing to remove skill outside Codewhale-owned roots");
    }
    if skill.source_kind != SkillSourceKind::CodeWhaleManaged {
        bail!("only Codewhale managed skills can be removed");
    }
    let skills_dir = validate_owned_skill_path(ctx, &skill, &path)?;
    let before = verify_expected_digest(&path, expected_digest.as_deref())?;
    let scope = match skill.root.kind {
        SkillRootKind::CodeWhaleProject => SkillScope::Project,
        SkillRootKind::CodeWhaleGlobal => SkillScope::Global,
        _ => SkillScope::Logical,
    };
    let package_name = on_disk_package_name(&skill_id)?;
    validate_owned_skill_path(ctx, &skill, &path)?;
    install::uninstall(package_name, &skills_dir)?;
    Ok(SkillMutationReceipt {
        action: SkillActionKind::Remove,
        name: skill_id.canonical_name,
        scope,

View on GitHub (pinned to 73e0f67d83)