{"record":{"id":"0389996b8091dbfa","repo":"siyuan-note/siyuan","slug":"unrecognized-skill-source-s","errorCode":null,"errorMessage":"unrecognized skill source: %s","messagePattern":"unrecognized skill source: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":603,"sourceCode":"func normalizeSkillURL(raw string) (normalizedSkillSource, error) {\n\traw = strings.TrimSpace(raw)\n\n\t// 1. 整条 \"npx skills add owner/repo ...\" 命令：提取 owner/repo\n\tif strings.Contains(raw, \"skills add\") || strings.Contains(raw, \"skills@\") {\n\t\tif m := skillsAddPattern.FindStringSubmatch(raw); len(m) == 2 {\n\t\t\treturn codeloadSource(m[1], \"main\"), nil\n\t\t}\n\t}\n\n\t// 2. owner/repo 简写（无 scheme、无点、单个 /）\n\tif !strings.Contains(raw, \"://\") && !strings.Contains(raw, \"//\") && ownerRepoPattern.MatchString(raw) {\n\t\treturn codeloadSource(raw, \"main\"), nil\n\t}\n\n\t// 3. 带 scheme 的 URL\n\tu, err := url.Parse(raw)\n\tif err != nil || u.Scheme == \"\" || u.Host == \"\" {\n\t\treturn normalizedSkillSource{}, fmt.Errorf(\"unrecognized skill source: %s\", raw)\n\t}\n\n\tswitch u.Host {\n\tcase \"github.com\":\n\t\treturn normalizeGitHubURL(u)\n\tcase \"raw.githubusercontent.com\":\n\t\t// 直接 GET 单个 SKILL.md（或其它文本文件）\n\t\treturn normalizedSkillSource{downloadURL: u.String()}, nil\n\tdefault:\n\t\t// 其它直链（release zip、自建站点等）：直接 GET，是否 zip 交由 Content-Type 判定\n\t\treturn normalizedSkillSource{downloadURL: u.String()}, nil\n\t}\n}\n\n// codeloadSource 构造 codeload zip 下载源，branch 用于 main→master 回退\nfunc codeloadSource(ownerRepo, branch string) normalizedSkillSource {\n\treturn normalizedSkillSource{\n\t\tdownloadURL: \"https://codeload.github.com/\" + ownerRepo + \"/zip/refs/heads/\" + branch,","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L585-L621","documentation":"normalizeSkillURL accepts owner/repo shorthand, bare owner/repo-like inputs, and full URLs with a scheme and host. If the input cannot be parsed as a URL with both scheme and host (and did not match earlier shorthand patterns), it is rejected as 'unrecognized skill source: %s'.","triggerScenarios":"Passing a string that is neither owner/repo shorthand nor an absolute http(s) URL — e.g. 'my folder/skill', 'ftp:thing', 'github.com/owner/repo' without scheme (depending on shorthand matching), or a malformed URL like 'http://'.","commonSituations":"Copy-pasting a partial path or local directory path instead of a repo/URL; omitting the https:// scheme; trailing spaces or newline in pasted input; non-Git URLs the normalizer does not recognize.","solutions":["Use the owner/repo shorthand form (e.g. 'owner/repo') for GitHub repos","Prefix URLs with the full scheme: 'https://github.com/...' or 'https://raw.githubusercontent.com/...'","Trim whitespace/newlines from pasted input before calling","Verify the string parses as an absolute URL with scheme and host"],"exampleFix":"// before\nInstallSkill(\"github.com/owner/repo\") // no scheme, not shorthand\n// after\nInstallSkill(\"owner/repo\")\n// or\nInstallSkill(\"https://github.com/owner/repo\")","handlingStrategy":"validation","validationCode":"function looksLikeSkillSource(s) {\n  if (/^[\\w.-]+\\/[\\w.-]+$/.test(s)) return true;            // owner/repo\n  try { const u = new URL(s); return !!u.host; } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await installSkill(raw);\n} catch (e) {\n  if (String(e.message).startsWith(\"unrecognized skill source\")) {\n    // normalize input: add https:// scheme or use owner/repo shorthand\n  }\n}","preventionTips":["Use owner/repo shorthand for GitHub repos","Always include https:// for full URLs","Trim whitespace/newlines from pasted input","Validate the URL parses with scheme and host before calling"],"tags":["url","validation","parsing"],"backgroundTag":"invalid-url-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}