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

  1. Ensure the path includes an assets/ segment, e.g. "assets/x.png" or "<boxID>/assets/x.png".
  2. For non-asset data files, use the appropriate file API instead of ResolveDataAssetPath.
  3. 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

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


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