Hmbown/CodeWhale · error
import source must be a compatible external skill
Error message
import source must be a compatible external skill
What it means
This error is thrown by `import_external` in the skills mutation API when the skill selected as the import source is not of `SkillSourceKind::CompatibleExternal`. Only skills audited as compatible external packages may be copied into Codewhale-owned directories; managed, bundled, or already-imported sources are rejected to keep provenance and trust markers correct.
Solutions
- Verify the source ID refers to an external (not managed/imported) skill by inspecting the audit listing before calling import.
- Re-scan/re-audit the skills directories so the external package is classified as `CompatibleExternal`; fix its SKILL.md metadata if classification failed.
- If the skill is already managed it does not need importing — update it instead, or remove it first for a fresh import.
- Check that the configured external/compatible skills directory actually contains the package you intended to import.
Example fix
// before mutator.import_external(&managed_skill_id, target, policy, &ctx)?; // rejects // after let audited = audit.find(&external_skill_id)?; assert_eq!(audited.source_kind, SkillSourceKind::CompatibleExternal); mutator.import_external(&external_skill_id, target, policy, &ctx)?;
Defensive patterns
Strategy: validation
Validate before calling
let (skill, _) = find_audited_skill(ctx, &source_id)?;
if skill.source_kind != SkillSourceKind::CompatibleExternal {
return Err(anyhow!("source {:?} is not a compatible external skill", source_id));
} Type guard
fn is_importable(skill: &AuditedSkill) -> bool {
matches!(skill.source_kind, SkillSourceKind::CompatibleExternal) && !skill.path_unsafe
} Prevention
- Check the audit listing's source_kind before importing.
- Never pass managed or imported skill IDs to import_external.
- Refresh the skill audit after changing external skill packages.
When it happens
Trigger: Calling `import_external` (via mutation `execute_sync`) with a `source_id` that resolves through `find_audited_skill` to a skill whose `source_kind != SkillSourceKind::CompatibleExternal` — e.g. importing a Codewhale-managed skill, an already-imported skill, or an external package that failed compatibility classification.
Common situations: Passing the ID of a project-owned or managed skill instead of an external one; double-importing a skill that is already in the owned directory (it is now managed, not external); the external package's SKILL.md metadata failed the compatible-external audit so it was classified differently.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- skill is not an import candidate
- bundle contains conflicting or rejected entries
- Choose one recording to import.
- Codewhale runtime event file is empty.
- destination already exists with different content
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d3d42cd0c348e3c4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/skills/mutation.rs:651
scope: target.as_skill_scope(),
safe_target_path: safe_display_path(&skills_dir, Some(ctx.workspace), ctx.home),
before_digest: None,
after_digest: None,
outcome: SkillMutationOutcome::NetworkDenied(host),
}),
}
}
fn import_external(
source_id: AuditedSkillId,
expected_digest: String,
target: SkillTargetScope,
conflict_policy: ConflictPolicy,
ctx: &MutationContext<'_>,
) -> Result<SkillMutationReceipt> {
let (source_skill, source_path) = find_audited_skill(ctx, &source_id)?;
if source_skill.source_kind != SkillSourceKind::CompatibleExternal {
bail!("import source must be a compatible external skill");
}
if source_skill.path_unsafe {
bail!("refusing to import unsafe external skill package");
}
let owned_snap = scan_with_configured(
ctx.workspace,
ctx.home,
ctx.configured_skills_dir,
SkillAuditMode::OwnedOnly,
None,
);
let owned_has_name = owned_snap
.skills
.iter()
.any(|s| s.id.canonical_name == source_id.canonical_name);
if !owned_has_name && !source_skill.import_candidate {View on GitHub (pinned to 73e0f67d83)