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)
		return

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Verify the notebook ID exists (check the notebooks API / conf.json on disk) before resolving.
  2. If the notebook was deleted, remove the dangling reference and re-resolve against a valid notebook.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/394ab542e84ecfb2. Report an issue: GitHub.