siyuan-note/siyuan · error
asset path must be under assets
Error message
asset path must be under assets
What it means
CreateAssetHistory only snapshots files that live inside the workspace's assets/ directory. After cleaning the input, the kernel requires the slash-relative path to start with assets/; anything else is rejected. This is the first of two layered path checks (the second verifies the absolute path is a true subpath of <data>/assets).
Solutions
- Pass a path relative to the data dir that begins with assets/, e.g. assets/image.png.
- Move or copy the file into the workspace assets/ directory first.
- Strip any absolute prefix so only the assets/... portion is passed.
Example fix
// before
CreateAssetHistory("/home/user/SiYuan/data/assets/pic.png")
// after
CreateAssetHistory("assets/pic.png") Defensive patterns
Strategy: validation
Validate before calling
const cleaned = path.posix.normalize(assetPath.replace(/\\/g, "/")).replace(/^\/+/, "");
if (!cleaned.startsWith("assets/")) {
throw new Error("asset path must be under assets: " + cleaned);
} Type guard
function isAssetRelPath(p) {
return path.posix.normalize(p.replace(/\\/g, "/")).replace(/^\/+/, "").startsWith("assets/");
} Prevention
- Always pass data-dir-relative asset paths starting with assets/.
- Convert absolute paths to relative before calling.
- Centralize asset path construction in one helper.
When it happens
Trigger: Calling CreateAssetHistory with a path like "images/foo.png", an absolute path whose cleaned form retains a leading drive/root so it no longer starts with assets/, an empty string, or a path outside the workspace assets folder.
Common situations: Passing absolute filesystem paths instead of workspace-relative asset paths; assets stored in custom locations outside data/assets; trailing leading slash removed incorrectly producing a non-asset prefix.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- asset path does not belong to a notebook
- asset path escapes data directory
- asset path is required
- asset path must be a file
- asset path [ ] is outside assets directory [ ]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/1fb5707693ba9110.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/history.go:918
})
return
}
func generateAssetsHistory() {
assets := recentModifiedAssets()
if 1 > len(assets) {
return
}
if err := createAssetsHistory(assets); err != nil {
logging.LogErrorf("generate assets history failed: %s", err)
}
}
// CreateAssetHistory 为指定资源文件创建历史快照。
func CreateAssetHistory(assetPath string) (err error) {
assetPath = strings.TrimPrefix(filepath.ToSlash(filepath.Clean(filepath.FromSlash(assetPath))), "/")
if !strings.HasPrefix(assetPath, "assets/") {
return errors.New("asset path must be under assets")
}
assetAbsPath := filepath.Join(util.DataDir, filepath.FromSlash(assetPath))
assetsDir := filepath.Join(util.DataDir, "assets")
if !gulu.File.IsSubPath(assetsDir, assetAbsPath) {
return errors.New("asset path must be under assets")
}
info, statErr := os.Stat(assetAbsPath)
if statErr != nil {
return statErr
}
if info.IsDir() {
return errors.New("asset path must be a file")
}
return createAssetsHistory([]string{assetAbsPath})
}
func createAssetsHistory(assets []string) (err error) {View on GitHub (pinned to 9f775e8a12)