{"record":{"id":"695b31db94b21f19","repo":"Hmbown/CodeWhale","slug":"skill-name-was-imported-locally-spec-an","errorCode":null,"errorMessage":"skill '{name}' was imported locally (spec '{}') and cannot be updated from a registry; re-import or remove it first","messagePattern":"skill '(.+?)' was imported locally \\(spec '(.+?)'\\) and cannot be updated from a registry; re-import or remove it first","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/install.rs","lineNumber":446,"sourceCode":"    skills_dir: &Path,\n    max_size: u64,\n    network: &NetworkPolicy,\n    registry_url: &str,\n) -> Result<UpdateResult> {\n    let target = skill_target_path(name, skills_dir)?;\n    if target.exists() {\n        ensure_target_within_skills_dir(&target, skills_dir)?;\n    }\n    let marker_path = target.join(INSTALLED_FROM_MARKER);\n    if !marker_path.exists() {\n        return Err(InstallError::NotInstalledHere(name.to_string()).into());\n    }\n    let marker_body = fs::read_to_string(&marker_path)\n        .with_context(|| format!(\"failed to read {}\", marker_path.display()))?;\n    let marker: InstalledFromMarker = serde_json::from_str(&marker_body)\n        .with_context(|| format!(\"malformed {INSTALLED_FROM_MARKER} for {name}\"))?;\n    if !is_registry_updatable_spec(&marker.spec) {\n        bail!(\n            \"skill '{name}' was imported locally (spec '{}') and cannot be updated from a registry; \\\n             re-import or remove it first\",\n            marker.spec\n        );\n    }\n\n    // Re-resolve the URL, taking the existing checksum as a short-circuit hint:\n    // we still hit the network so the user gets a useful \"no upstream change\"\n    // signal, but we skip the unpack step if the bytes match.\n    let source = InstallSource::parse(&marker.spec)?;\n    let urls = match candidate_urls(&source, network, registry_url).await? {\n        UrlResolution::Resolved(urls) => urls,\n        UrlResolution::NeedsApproval(host) => return Ok(UpdateResult::NeedsApproval(host)),\n        UrlResolution::Denied(host) => return Ok(UpdateResult::NetworkDenied(host)),\n    };\n    let (bytes, _url) = match download_first_success(&urls, network, max_size).await? {\n        DownloadOutcome::Bytes { bytes, url } => (bytes, url),\n        DownloadOutcome::NeedsApproval(host) => return Ok(UpdateResult::NeedsApproval(host)),","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/install.rs#L428-L464","documentation":"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.","triggerScenarios":"'/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.","commonSituations":"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.","solutions":["Remove the skill ('/skill uninstall <name>') and install it fresh from the registry spec.","Or re-import it deliberately from the local source to pick up the new content.","Inspect the .installed-from file under the skill directory to see the recorded spec before deciding.","Do not hand-edit the marker to force updates; the spec drives checksum verification."],"exampleFix":"# before\n/skill update my-skill\n# -> was imported locally (spec 'file:///opt/my-skill')\n\n# after\n/skill uninstall my-skill\n/skill install my-skill","handlingStrategy":"fallback","validationCode":"// Before updating, read the marker and check the spec origin\nlet marker: InstalledFromMarker =\n    serde_json::from_str(&std::fs::read_to_string(dir.join(\".installed-from\"))?)?;\nlet registry_updatable = !marker.spec.starts_with(\"file:\"); // mirror is_registry_updatable_spec\nif !registry_updatable {\n    // choose re-import or uninstall+install instead of update\n}","typeGuard":null,"tryCatchPattern":"match skills::install::update(&name, &skills_dir, &network, &registry_url).await {\n    Err(err) if err.to_string().contains(\"cannot be updated from a registry\") => {\n        // fallback: uninstall then install fresh from the registry\n        skills::install::uninstall(&name, &skills_dir)?;\n        skills::install::install(&format!(\"github:{name}\"), &skills_dir, &network).await?;\n    }\n    other => other?,\n}","preventionTips":["Prefer registry installs when you plan to use '/skill update' long-term.","Keep local imports in a separately named skill so blanket updates skip them.","Check the .installed-from marker before bulk update scripts."],"tags":["skills","update","registry","install-marker","rust"],"backgroundTag":"local-install-not-registry-updatable","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}