siyuan-note/siyuan · error
path is not under an assets directory: %s
Error message
path is not under an assets directory: %s
What it means
Returned by ResolveDataAssetPath when no "assets" segment can be found in the cleaned data-relative path (assetDirIndex stays -1). The resolver requires either a top-level assets/ dir (parts[0]=="assets") or a notebook-prefixed assets dir (<id>/.../assets/...); any path without an assets component is rejected before symlink resolution.
Source
Thrown at kernel/model/assets.go:938
if parts[i] == "assets" {
assetDirIndex = i
break
}
}
if assetDirIndex > 0 {
boxConfPath := filepath.Join(util.DataDir, parts[0], ".siyuan", "conf.json")
if !filelock.IsExist(boxConfPath) {
err = fmt.Errorf("asset path does not belong to a notebook: %s", assetPath)
return
}
if IsEncryptedBox(parts[0]) {
err = fmt.Errorf("accessing assets in encrypted notebook [%s] is not supported", parts[0])
return
}
}
}
if assetDirIndex < 0 {
err = fmt.Errorf("path is not under an assets directory: %s", assetPath)
return
}
assetRootParts := parts[:assetDirIndex+1]
assetRoot := filepath.Join(util.DataDir, filepath.FromSlash(strings.Join(assetRootParts, "/")))
if !gulu.File.IsSubPath(assetRoot, absPath) {
err = fmt.Errorf("path is not a child of assets directory: %s", assetPath)
return
}
resolvedRoot, evalErr := filepath.EvalSymlinks(assetRoot)
if evalErr != nil {
err = fmt.Errorf("resolve assets directory [%s] failed: %w", assetRoot, evalErr)
return
}
if assetDirIndex > 0 {
notebookRoot := filepath.Join(util.DataDir, parts[0])
resolvedDataDir, dataEvalErr := filepath.EvalSymlinks(util.DataDir)View on GitHub (pinned to 251596fc0d)
Solutions
- Ensure the path includes an assets/ segment, e.g. "assets/x.png" or "<boxID>/assets/x.png".
- For non-asset data files, use the appropriate file API instead of ResolveDataAssetPath.
- Build asset paths only from values returned by the asset insertion/listing APIs.
Example fix
// before
rel, abs, err := model.ResolveDataAssetPath("<boxID>/20200101.siyuan") // no assets segment
// after
rel, abs, err := model.ResolveDataAssetPath("<boxID>/assets/image-20200101.png") Defensive patterns
Strategy: validation
Validate before calling
parts := strings.Split(filepath.ToSlash(filepath.Clean(assetPath)), "/")
hasAssets := false
for _, p := range parts {
if p == "assets" { hasAssets = true; break }
}
if !hasAssets {
return fmt.Errorf("path is not under an assets directory: %s", assetPath)
} Prevention
- Only call ResolveDataAssetPath for asset files — never for .sy docs or config files.
- Always include the assets/ segment in constructed paths.
- Derive asset paths from insertion/listing APIs rather than building them by hand.
When it happens
Trigger: Passing "docs/note.md", "foo/bar.png", "<boxID>/data/x.sy", or any path that never enters an assets directory. The resolver is asset-only by design.
Common situations: Caller used ResolveDataAssetPath for a non-asset file (a .sy doc, a config file); a path constructed without the assets/ prefix; confusion between ResolveDataAssetPath and a general data-path resolver.
Related errors
- assetPath is required for analyze
- only local assets/... images are supported
- assetPath is not an image referenced by the document
- asset path is required
- asset path must be relative to data directory: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/7e4b3a0fbafc5fda.
Report an issue: GitHub.