siyuan-note/siyuan · error

unrecognized skill source: %s

Error message

unrecognized skill source: %s

What it means

Thrown by normalizeSkillURL when the input is neither an 'npx skills add' command, an owner/repo shorthand, nor a parseable URL with scheme and host. It is the catch-all rejection for inputs that do not match any supported source format.

Source

Thrown at kernel/util/skill.go:301

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 251596fc0d)

Solutions

  1. Provide a full URL starting with https://, an owner/repo shorthand, or the full 'npx skills add owner/repo' command.
  2. Ensure owner/repo shorthand has no scheme, no '//' and exactly one slash with no dots in either segment.
  3. Validate and pre-format the input in the UI before submission.

Example fix

// before
util.InstallSkill("myskill")

// after — valid shorthand or full URL
util.InstallSkill("owner/repo")
// or
util.InstallSkill("https://github.com/owner/repo")
Defensive patterns

Strategy: validation

Validate before calling

import "net/url"

func isValidSkillSource(raw string) bool {
    raw = strings.TrimSpace(raw)
    if raw == "" {
        return false
    }
    if strings.Contains(raw, "skills add") || strings.Contains(raw, "skills@") {
        return true
    }
    if !strings.Contains(raw, "://") && !strings.Contains(raw, "//") && ownerRepoPattern.MatchString(raw) {
        return true
    }
    u, err := url.Parse(raw)
    return err == nil && u.Scheme != "" && u.Host != ""
}

Prevention

When it happens

Trigger: Passing a bare string with no scheme (e.g. 'myskill'), a malformed URL (e.g. 'htp://example'), a URL with empty host, or a value containing '://' that url.Parse rejects. The owner/repo regex also requires no dots and exactly one slash, so 'example.com/x' bypasses it.

Common situations: User typed a skill name instead of a source; missing https:// prefix; copy-paste introduced a stray character; the shorthand regex rejected input containing dots or multiple slashes.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/0389996b8091dbfa. Report an issue: GitHub.