siyuan-note/siyuan · error
asset path does not belong to a notebook: %s
Error message
asset path does not belong to a notebook: %s
What it means
Returned by ResolveDataAssetPath when the first path segment matches ast.IsNodeIDPattern (looks like a notebook ID) and an assets/ segment exists under it, but <data>/<id>/.siyuan/conf.json does not exist on disk — meaning that notebook ID is not actually a registered notebook. The offending path is interpolated.
Source
Thrown at kernel/model/assets.go:928
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]):
for i := 1; i < len(parts)-1; i++ {
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)
returnView on GitHub (pinned to 251596fc0d)
Solutions
- Verify the notebook ID exists (check the notebooks API / conf.json on disk) before resolving.
- If the notebook was deleted, remove the dangling reference and re-resolve against a valid notebook.
- Switch to the workspace that actually contains that notebook.
Example fix
// before
rel, abs, err := model.ResolveDataAssetPath("20200101000000-xx/assets/a.png")
// after — confirm the notebook exists first
boxConfPath := filepath.Join(util.DataDir, "20200101000000-xx", ".siyuan", "conf.json")
if !filelock.IsExist(boxConfPath {
return errors.New("notebook not mounted in this workspace")
}
rel, abs, err := model.ResolveDataAssetPath("20200101000000-xx/assets/a.png") Defensive patterns
Strategy: validation
Validate before calling
parts := strings.Split(filepath.ToSlash(filepath.Clean(assetPath)), "/")
if len(parts) > 2 && ast.IsNodeIDPattern(parts[0]) {
boxConf := filepath.Join(util.DataDir, parts[0], ".siyuan", "conf.json")
if !filelock.IsExist(boxConf) {
return fmt.Errorf("notebook %s not present in workspace", parts[0])
}
} Prevention
- Confirm the notebook is mounted before resolving paths into it.
- Switch to the correct workspace if referencing a notebook from another workspace.
- Drop stale references to deleted notebooks.
When it happens
Trigger: Passing a path like "20200101000000-abcdef/assets/x.png" where the notebook 20200101000000-abcdef does not exist in this workspace; a notebook was deleted but its assets remain; a workspace switch left stale references.
Common situations: Cross-workspace path leakage; a stale bookmark/reference to a deleted notebook; a typo or truncated notebook ID.
Related errors
- invalid box id
- path belongs to encrypted notebook [%s]: %s
- --notebook is required
- --notebook is required
- parent path not found: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/394ab542e84ecfb2.
Report an issue: GitHub.