siyuan-note/siyuan · warning

invalid asset path

Error message

invalid asset path: %q

What it means

relinkPath parses an asset URL reference before matching it to a file on disk. The first guard rejects values that fail url.Parse, are absolute URLs, or carry a host or opaque part — such references are not local workspace assets and cannot be relinked.

Solutions

  1. Replace external URL references in the document with locally imported assets (assets/... paths)
  2. Skip or preprocess non-local references before invoking the relinker
  3. If the URL should be local, remove the scheme/host portion and ensure the value is a relative assets/ path

Example fix

// before
relink("https://example.com/img.png")
// after
relink("assets/img-20240101120000-ab12cd3.png")
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(v)
if err != nil || u.IsAbs() || u.Host != "" || u.Opaque != "" { /* not a local asset reference */ }

Type guard

func isRelativeAssetRef(v string) bool { u, err := url.Parse(v); return err == nil && !u.IsAbs() && u.Host == "" && u.Opaque == "" }

Try / catch

if _, err := relinkPath(v); err != nil { log.Warnf("skipping non-local ref %s: %v", v, err); return "" }

Prevention

When it happens

Trigger: Calling newAssetRelinker, reference, or list with an asset value like "https://example.com/img.png", "mailto:a@b", or a malformed value that url.Parse rejects.

Common situations: Documents containing external network images or protocol links pasted as asset references; clippings that stored absolute URLs; data migrated from other tools that use full URLs.

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/ab900c3d1d716c24. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/asset_relink_references.go:40

	xhtml "golang.org/x/net/html"
)

type assetRelinker struct {
	oldPath     string
	newPath     string
	result      apicontract.AssetReferencesData
	routes      map[string]*assetRelinker
	collectOnly bool
	recordless  bool
	disabled    bool
	changes     int
}

// relinkPath 按 URL 路径解码一次,查询参数和片段不参与资源文件名匹配。
func relinkPath(value string) (string, error) {
	u, err := url.Parse(value)
	if err != nil || u.IsAbs() || u.Host != "" || u.Opaque != "" {
		return "", fmt.Errorf("invalid asset path: %q", value)
	}
	p := strings.TrimPrefix(u.Path, "/")
	if !strings.HasPrefix(p, "assets/") || path.Clean(p) != p || strings.ContainsAny(p, "\\\x00\r\n") || strings.HasSuffix(p, "/") {
		return "", fmt.Errorf("invalid asset path: %q", value)
	}
	for _, segment := range strings.Split(p, "/") {
		if strings.Contains(segment, ":") {
			return "", fmt.Errorf("invalid asset path: %q", value)
		}
	}
	return p, nil
}

func (r *assetRelinker) reference(raw, kind string, location apicontract.AssetReference) string {
	link, annotationID := raw, ""
	if kind == "annotation" {
		link, annotationID = util.SplitFileAnnotationRef(raw)
		if link == "" {

View on GitHub (pinned to 9f775e8a12)