siyuan-note/siyuan · error
invalid github URL
Error message
invalid github URL: %s
What it means
normalizeGitHubURL rejects a github.com URL whose path does not contain at least an owner and a repository segment. The library parses the URL path into slash-separated parts and needs at least 2 parts to build the owner/repo pair it downloads from. Any github.com URL without owner/repo (e.g. bare github.com or github.com/owner) is invalid.
Solutions
- Provide a full github.com URL containing both owner and repository, e.g. https://github.com/owner/repo
- Check for stray slashes or truncation when pasting the URL; remove empty segments (a leading '//' produces empty parts)
- If pointing at a single file, use the full blob URL (https://github.com/owner/repo/blob/<branch>/<path>) or a raw.githubusercontent.com URL
- If using a non-GitHub source, pass its direct URL (raw.githubusercontent.com or any direct link) instead of a bare host
Example fix
// before
InstallSkill("https://github.com/siyuan-note")
// after
InstallSkill("https://github.com/siyuan-note/siyuan") Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(src)
if (u.hostname !== "github.com") throw new Error("not a github.com URL")
const parts = u.pathname.replace(/^\/+/, "").split("/").filter(Boolean)
if (parts.length < 2) throw new Error("github URL needs /<owner>/<repo>") Type guard
function isGitHubRepoUrl(src) {
try {
const u = new URL(src)
return u.hostname === "github.com" &&
u.pathname.replace(/^\/+/, "").split("/").filter(Boolean).length >= 2
} catch { return false }
} Try / catch
try {
await installSkill(src)
} catch (e) {
if (String(e).includes("invalid github URL")) {
showHint("Use a full URL: https://github.com/<owner>/<repo>")
}
} Prevention
- Always paste complete repo URLs including owner and repo segments
- Filter empty path segments before validating (protects against double slashes)
- UI: pre-validate the input field before submitting the install request
When it happens
Trigger: Calling InstallSkill with a github.com URL whose path splits into fewer than 2 segments, e.g. https://github.com, https://github.com/, or https://github.com/owner (missing the repo name).
Common situations: Truncated copy-paste of a repo URL; stripping the repo segment when templating URLs; typo like https://github.com//repo producing empty parts; pasting a GitHub org page or search URL instead of a repo URL.
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
- CalDAV: calendar object path is invalid
- CalDAV: calendar path is invalid
- CardDAV: address book path is invalid
- CardDAV: path is invalid
- data?.msg || data?.message ||…
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/4b4cbadba6a07e32.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:635
// codeloadSource 构造 codeload zip 下载源,branch 用于 main→master 回退
func codeloadSource(ownerRepo, branch string) normalizedSkillSource {
return normalizedSkillSource{
downloadURL: "https://codeload.github.com/" + ownerRepo + "/zip/refs/heads/" + branch,
isZip: true,
branch: branch,
}
}
// normalizeGitHubURL 处理 github.com 的各种路径形态
func normalizeGitHubURL(u *url.URL) (normalizedSkillSource, error) {
// /owner/repo/tree/<branch> 或 /owner/repo/tree/<branch>/<path>
// /owner/repo/commit/<sha>
// /owner/repo/releases/download/<tag>/<asset>
// /owner/repo(默认分支)
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) < 2 {
return normalizedSkillSource{}, fmt.Errorf("invalid github URL: %s", u.String())
}
ownerRepo := parts[0] + "/" + parts[1]
// releases/download/<tag>/<asset>
if len(parts) >= 6 && parts[2] == "releases" && parts[3] == "download" {
asset := parts[5]
// 是否 zip 交由 Content-Type 最终判定,这里仅按 asset 后缀预判
return normalizedSkillSource{downloadURL: u.String(), isZip: strings.HasSuffix(asset, ".zip")}, nil
}
// tree/<branch>[/path] 或 blob/<branch>/...
if len(parts) >= 4 && (parts[2] == "tree" || parts[2] == "blob") {
branch := parts[3]
if parts[2] == "blob" {
// blob 指向单个文件,走 raw
rawPath := strings.Join(parts[4:], "/")
return normalizedSkillSource{
downloadURL: "https://raw.githubusercontent.com/" + ownerRepo + "/" + branch + "/" + rawPath,View on GitHub (pinned to 9f775e8a12)