siyuan-note/siyuan · error

invalid download URL: %s

Error message

invalid download URL: %s

What it means

Thrown by downloadSkillSource when the already-normalized download URL fails url.Parse or has an empty host. This is a defensive check on an internally-constructed URL; reaching it usually indicates a bug in normalizeSkillURL/normalizeGitHubURL producing a malformed downloadURL.

Source

Thrown at kernel/util/skill.go:374

	// commit/<sha>
	if len(parts) >= 4 && parts[2] == "commit" {
		sha := parts[3]
		return normalizedSkillSource{
			downloadURL: "https://codeload.github.com/" + ownerRepo + "/zip/" + sha,
			isZip:       true,
		}, nil
	}

	// 纯仓库地址:默认 main,失败回退 master
	return codeloadSource(ownerRepo, "main"), nil
}

// downloadSkillSource 下载 skill 源,返回字节、Content-Type
func downloadSkillSource(src normalizedSkillSource) (data []byte, contentType string, err error) {
	u, perr := url.Parse(src.downloadURL)
	if perr != nil || u.Host == "" {
		return nil, "", fmt.Errorf("invalid download URL: %s", src.downloadURL)
	}
	if cerr := CheckHostSSRF(u.Hostname()); cerr != nil {
		return nil, "", cerr
	}

	data, contentType, err = fetchBytes(src.downloadURL)
	if err == nil {
		return data, contentType, nil
	}

	// codeload main 分支 404 时回退 master
	if src.isZip && src.branch == "main" {
		ownerRepo := strings.TrimPrefix(strings.TrimPrefix(src.downloadURL, "https://codeload.github.com/"), "http://codeload.github.com/")
		ownerRepo = strings.TrimSuffix(ownerRepo, "/zip/refs/heads/main")
		fallback := codeloadSource(ownerRepo, "master")
		data, contentType, ferr := fetchBytes(fallback.downloadURL)
		if ferr != nil {
			return nil, "", fmt.Errorf("download failed (tried main and master): %v", err)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Inspect the normalizedSkillSource.downloadURL produced by normalizeSkillURL for the given input.
  2. Ensure normalizeGitHubURL always returns a populated downloadURL for every accepted branch.
  3. Report the input that triggered it so the normalization gap can be fixed upstream.
Defensive patterns

Strategy: try-catch

Try / catch

// defensive: surface the malformed internal URL for diagnosis
res, err := util.InstallSkill(src)
if err != nil && strings.HasPrefix(err.Error(), "invalid download URL") {
    logging.LogErrorf("normalizeSkillURL produced a bad downloadURL for input %q", src)
}

Prevention

When it happens

Trigger: A code path constructs a normalizedSkillSource with an empty or malformed downloadURL (e.g. a codeload URL built from an empty ownerRepo); the SSRF/host check then cannot proceed. Not typically reachable from valid user input.

Common situations: A future refactor breaks URL construction; an unexpected branch in normalizeGitHubURL returns an empty downloadURL; a custom (non-github) host produced a URL without a host component.

Related errors


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