fix(skills): use slug for skill download URL from ClawHub (#502)

* fix(web): use slug for skill download URL from ClawHub

The skill install handler was using req.name (display name like
"Markdown Converter") instead of the slug (like "owner/markdown-converter")
when constructing the download URL. The registry endpoint expects a slug,
so display names caused 502 errors.

- Add optional `slug` field to SkillInstallRequest
- Prefer slug over name when building the download URL
- JS installSkill() now sends slug from search results

Closes #482

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: guard against empty slug string in skill download URL

Filter out empty slug strings so we fall back to name instead of
constructing an invalid download URL.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* style: cargo fmt

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Zaki Manian
2026-03-03 23:53:16 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent d562dc8d90
commit b60e5e907a
3 changed files with 12 additions and 2 deletions
+8 -1
View File
@@ -148,7 +148,14 @@ pub async fn skills_install_handler(
.await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?
} else if let Some(ref catalog) = state.skill_catalog {
let url = crate::skills::catalog::skill_download_url(catalog.registry_url(), &req.name);
// Prefer slug (e.g. "owner/skill-name") over display name for the
// download URL, since the registry endpoint expects a slug.
let download_key = req
.slug
.as_deref()
.filter(|s| !s.is_empty())
.unwrap_or(&req.name);
let url = crate::skills::catalog::skill_download_url(catalog.registry_url(), download_key);
crate::tools::builtin::skill_tools::fetch_skill_content(&url)
.await
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?
+1 -1
View File
@@ -3580,7 +3580,7 @@ function formatTimeAgo(epochMs) {
}
function installSkill(nameOrSlug, url, btn) {
var body = { name: nameOrSlug };
var body = { name: nameOrSlug, slug: nameOrSlug };
if (url) body.url = url;
apiFetch('/api/skills/install', {
+3
View File
@@ -569,6 +569,9 @@ pub struct SkillSearchResponse {
#[derive(Debug, Deserialize)]
pub struct SkillInstallRequest {
pub name: String,
/// Registry slug (e.g. "owner/skill-name"). Preferred over `name` for
/// constructing the download URL when fetching from ClawHub.
pub slug: Option<String>,
pub url: Option<String>,
pub content: Option<String>,
}