Hmbown/CodeWhale · error

refusing to update skill outside Codewhale-owned roots

Error message

refusing to update skill outside Codewhale-owned roots

What it means

`update_skill` only mutates skills living in roots marked writable/Codewhale-owned (`root.is_writable_owned()`). Bundled skills, external skills, and read-only locations are refused, since Codewhale must not edit content it does not own.

Solutions

  1. Import the external skill into an owned scope first, then update the imported copy.
  2. Target a skill that lives under a writable Codewhale-owned skills directory.
  3. Fix the skills directory configuration/permissions so the root is writable and recognized as owned.

Example fix

// before
mutation.execute(Mutation::Update { skill_id: bundled_skill_id, .. })?; // rejected
// after
mutation.execute_sync(Mutation::ImportExternal { source_id: external_id, target, conflict_policy })?;
mutation.execute(Mutation::Update { skill_id: imported_id, .. })?;
Defensive patterns

Strategy: validation

Validate before calling

let (skill, _) = find_audited_skill(ctx, &skill_id)?;
if !skill.root.is_writable_owned() {
    return Err(anyhow!("skill root is not a writable owned root; import first"));
}

Type guard

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

Prevention

When it happens

Trigger: Calling the `Update` mutation (`execute` → `update_skill`) for a skill whose root is not a writable owned root — a bundled skill, an unimported external skill, or an owned dir mounted read-only.

Common situations: Trying to update an external skill instead of importing it first; pointing the skills dir at a read-only path (NFS, container image layer); passing the ID of a bundled example skill.

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/d6b1a9888bbf3478. Report an issue: GitHub.

Appendix: source

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

            copy_dir_regular_files(&path, &target)?;
        } else if meta.is_file() {
            if name_str.starts_with('.') {
                continue;
            }
            fs::copy(&path, &target)?;
        }
    }
    Ok(())
}

async fn update_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 update skill outside Codewhale-owned roots");
    }
    if skill.source_kind != SkillSourceKind::CodeWhaleManaged {
        bail!("only Codewhale managed skills can be updated");
    }
    let skills_dir = validate_owned_skill_path(ctx, &skill, &path)?;
    // Imported skills carry `import:…` provenance and must not hit the registry.
    ensure_remote_updatable(&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)?;
    let outcome = install::update_with_registry(
        package_name,

View on GitHub (pinned to 73e0f67d83)