siyuan-note/siyuan · error
asset path must be relative to data directory
Error message
asset path must be relative to data directory: %s
What it means
ResolveDataAssetPath only accepts paths relative to the workspace data directory. After converting to the OS-native form, it rejects any input that is absolute (filepath.IsAbs), carries a Windows volume name (C:...), or starts with a path separator. The error echoes the offending path so the caller can correct it.
Solutions
- Strip the data directory prefix and pass only the remainder, e.g. "assets/img.png" instead of "/abs/path/to/workspace/data/assets/img.png"
- Use filepath.Rel(util.DataDir, absPath) to convert an absolute path you already have into the required data-relative form before calling
- Remove leading slashes / file:// scheme from the input; for URLs, extract and decode the path portion first
- If the path comes from GetAssetAbsPath or similar, use the corresponding relative-path accessor instead
Example fix
// before
rel, abs, err := model.ResolveDataAssetPath("/home/me/siyuan/data/assets/pic.png")
// after
relPath, _ := filepath.Rel(util.DataDir, "/home/me/siyuan/data/assets/pic.png") // "assets/pic.png"
rel, abs, err := model.ResolveDataAssetPath(filepath.ToSlash(relPath)) Defensive patterns
Strategy: validation
Validate before calling
func toDataRelative(absPath, dataDir string) (string, error) {
rel, err := filepath.Rel(dataDir, absPath)
if err != nil || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
return "", fmt.Errorf("not under data dir: %s", absPath)
}
return filepath.ToSlash(rel), nil
} Type guard
func isRelative(p string) bool {
n := filepath.FromSlash(p)
return !filepath.IsAbs(n) && filepath.VolumeName(n) == "" && !os.IsPathSeparator(n[0])
} Try / catch
rel, abs, err := model.ResolveDataAssetPath(p)
if err != nil {
if strings.HasPrefix(err.Error(), "asset path must be relative") {
p = strings.TrimPrefix(p, util.DataDir+string(filepath.Separator))
rel, abs, err = model.ResolveDataAssetPath(p)
}
if err != nil { return err }
} Prevention
- Store and transmit asset paths in data-relative slash form everywhere in your code
- Never call filepath.Join(util.DataDir, ...) before handing the path to the resolver
- On Windows, remember drive letters make a path absolute; strip them at ingestion
- Convert API-returned absolute paths back to relative form before re-submitting them
When it happens
Trigger: Passing an absolute path like "/home/user/SiYuan/data/assets/img.png" or "C:\\data\\assets\\img.png", a path starting with a leading slash even if otherwise relative ("/assets/img.png"), or a full file:// URL to ResolveDataAssetPath (directly or through assetStat, PrepareAgentMessageImage, ResolveUnusedDataAssetPath, etc.).
Common situations: Scripts or plugins use paths copied from the OS file manager or from an API response containing absolute paths; code builds paths with filepath.Join(util.DataDir, ...) before calling the resolver, double-qualifying the path; Windows clients send backslash-prefixed drive paths.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/aab882f460cda625.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1032
}
return
}
func GetAssetAbsPath(relativePath string) (string, error) {
return GetAssetAbsPathWithOpt(relativePath, false)
}
// ResolveDataAssetPath 解析 data 相对资源路径,并确保目标位于全局或普通笔记本的资源目录中。
func ResolveDataAssetPath(assetPath string) (relativePath, absPath string, err error) {
if assetPath == "" {
err = errors.New("asset path is required")
return
}
nativePath := filepath.FromSlash(assetPath)
if filepath.IsAbs(nativePath) || filepath.VolumeName(nativePath) != "" ||
(len(nativePath) > 0 && os.IsPathSeparator(nativePath[0])) {
err = fmt.Errorf("asset path must be relative to data directory: %s", assetPath)
return
}
nativePath = filepath.Clean(nativePath)
absPath = filepath.Join(util.DataDir, nativePath)
dataRelativePath, relErr := filepath.Rel(util.DataDir, absPath)
if relErr != nil || dataRelativePath == "." || dataRelativePath == ".." ||
strings.HasPrefix(dataRelativePath, ".."+string(filepath.Separator)) {
err = fmt.Errorf("asset path escapes data directory: %s", assetPath)
return
}
parts := strings.Split(filepath.ToSlash(dataRelativePath), "/")
assetDirIndex := -1
switch {
case len(parts) > 1 && parts[0] == "assets":
assetDirIndex = 0
case len(parts) > 2 && ast.IsNodeIDPattern(parts[0]):View on GitHub (pinned to 9f775e8a12)