Hmbown/CodeWhale · error

skill '{name}' was imported locally (spec '{}') and cannot b

Error message

skill '{name}' was imported locally (spec '{}') and cannot be updated from a registry; re-import or remove it first

What it means

The skill update path refuses to update a skill whose .installed-from marker records a non-registry-updatable spec (a local import or direct source). Update is registry-driven by contract, so a locally imported skill must be re-imported or removed first. The marker's actual spec is echoed in the message so the origin is visible.

Source

Thrown at crates/tui/src/skills/install.rs:446

    skills_dir: &Path,
    max_size: u64,
    network: &NetworkPolicy,
    registry_url: &str,
) -> Result<UpdateResult> {
    let target = skill_target_path(name, skills_dir)?;
    if target.exists() {
        ensure_target_within_skills_dir(&target, skills_dir)?;
    }
    let marker_path = target.join(INSTALLED_FROM_MARKER);
    if !marker_path.exists() {
        return Err(InstallError::NotInstalledHere(name.to_string()).into());
    }
    let marker_body = fs::read_to_string(&marker_path)
        .with_context(|| format!("failed to read {}", marker_path.display()))?;
    let marker: InstalledFromMarker = serde_json::from_str(&marker_body)
        .with_context(|| format!("malformed {INSTALLED_FROM_MARKER} for {name}"))?;
    if !is_registry_updatable_spec(&marker.spec) {
        bail!(
            "skill '{name}' was imported locally (spec '{}') and cannot be updated from a registry; \
             re-import or remove it first",
            marker.spec
        );
    }

    // Re-resolve the URL, taking the existing checksum as a short-circuit hint:
    // we still hit the network so the user gets a useful "no upstream change"
    // signal, but we skip the unpack step if the bytes match.
    let source = InstallSource::parse(&marker.spec)?;
    let urls = match candidate_urls(&source, network, registry_url).await? {
        UrlResolution::Resolved(urls) => urls,
        UrlResolution::NeedsApproval(host) => return Ok(UpdateResult::NeedsApproval(host)),
        UrlResolution::Denied(host) => return Ok(UpdateResult::NetworkDenied(host)),
    };
    let (bytes, _url) = match download_first_success(&urls, network, max_size).await? {
        DownloadOutcome::Bytes { bytes, url } => (bytes, url),
        DownloadOutcome::NeedsApproval(host) => return Ok(UpdateResult::NeedsApproval(host)),

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Remove the skill ('/skill uninstall <name>') and install it fresh from the registry spec.
  2. Or re-import it deliberately from the local source to pick up the new content.
  3. Inspect the .installed-from file under the skill directory to see the recorded spec before deciding.
  4. Do not hand-edit the marker to force updates; the spec drives checksum verification.

Example fix

# before
/skill update my-skill
# -> was imported locally (spec 'file:///opt/my-skill')

# after
/skill uninstall my-skill
/skill install my-skill
Defensive patterns

Strategy: fallback

Validate before calling

// Before updating, read the marker and check the spec origin
let marker: InstalledFromMarker =
    serde_json::from_str(&std::fs::read_to_string(dir.join(".installed-from"))?)?;
let registry_updatable = !marker.spec.starts_with("file:"); // mirror is_registry_updatable_spec
if !registry_updatable {
    // choose re-import or uninstall+install instead of update
}

Try / catch

match skills::install::update(&name, &skills_dir, &network, &registry_url).await {
    Err(err) if err.to_string().contains("cannot be updated from a registry") => {
        // fallback: uninstall then install fresh from the registry
        skills::install::uninstall(&name, &skills_dir)?;
        skills::install::install(&format!("github:{name}"), &skills_dir, &network).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: '/skill update <name>' where the skill was installed via local import or a direct URL rather than from the registry; the is_registry_updatable_spec check on the marker fails and this bails before any network call.

Common situations: Teams where some skills came from a local checkout and someone later runs a blanket update; migrating from URL-based installs to the registry; markers written by older versions with non-registry specs.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/695b31db94b21f19. Report an issue: GitHub.