siyuan-note/siyuan · error

unrecognized skill source

Error message

unrecognized skill source: %s

What it means

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'.

Solutions

  1. Use the owner/repo shorthand form (e.g. 'owner/repo') for GitHub repos
  2. Prefix URLs with the full scheme: 'https://github.com/...' or 'https://raw.githubusercontent.com/...'
  3. Trim whitespace/newlines from pasted input before calling
  4. Verify the string parses as an absolute URL with scheme and host

Example fix

// before
InstallSkill("github.com/owner/repo") // no scheme, not shorthand
// after
InstallSkill("owner/repo")
// or
InstallSkill("https://github.com/owner/repo")
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeSkillSource(s) {
  if (/^[\w.-]+\/[\w.-]+$/.test(s)) return true;            // owner/repo
  try { const u = new URL(s); return !!u.host; } catch { return false; }
}

Try / catch

try {
  await installSkill(raw);
} catch (e) {
  if (String(e.message).startsWith("unrecognized skill source")) {
    // normalize input: add https:// scheme or use owner/repo shorthand
  }
}

Prevention

When it happens

Trigger: 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://'.

Common situations: 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.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/0389996b8091dbfa. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/skill.go:603

func normalizeSkillURL(raw string) (normalizedSkillSource, error) {
	raw = strings.TrimSpace(raw)

	// 1. 整条 "npx skills add owner/repo ..." 命令:提取 owner/repo
	if strings.Contains(raw, "skills add") || strings.Contains(raw, "skills@") {
		if m := skillsAddPattern.FindStringSubmatch(raw); len(m) == 2 {
			return codeloadSource(m[1], "main"), nil
		}
	}

	// 2. owner/repo 简写(无 scheme、无点、单个 /)
	if !strings.Contains(raw, "://") && !strings.Contains(raw, "//") && ownerRepoPattern.MatchString(raw) {
		return codeloadSource(raw, "main"), nil
	}

	// 3. 带 scheme 的 URL
	u, err := url.Parse(raw)
	if err != nil || u.Scheme == "" || u.Host == "" {
		return normalizedSkillSource{}, fmt.Errorf("unrecognized skill source: %s", raw)
	}

	switch u.Host {
	case "github.com":
		return normalizeGitHubURL(u)
	case "raw.githubusercontent.com":
		// 直接 GET 单个 SKILL.md(或其它文本文件)
		return normalizedSkillSource{downloadURL: u.String()}, nil
	default:
		// 其它直链(release zip、自建站点等):直接 GET,是否 zip 交由 Content-Type 判定
		return normalizedSkillSource{downloadURL: u.String()}, nil
	}
}

// codeloadSource 构造 codeload zip 下载源,branch 用于 main→master 回退
func codeloadSource(ownerRepo, branch string) normalizedSkillSource {
	return normalizedSkillSource{
		downloadURL: "https://codeload.github.com/" + ownerRepo + "/zip/refs/heads/" + branch,

View on GitHub (pinned to 9f775e8a12)