siyuan-note/siyuan · error

resolve assets directory [%s] failed: %w

Error message

resolve assets directory [%s] failed: %w

What it means

Thrown by ResolveDataAssetPath (kernel/model/assets.go:951) when filepath.EvalSymlinks fails on the reconstructed assets root directory (either global `data/assets/` or a notebook's `<boxID>/assets/`). EvalSymlinks fails when the directory does not exist, when any link in the chain is broken, or when the process lacks permission to traverse a component.

Source

Thrown at kernel/model/assets.go:951

				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)
		resolvedNotebookRoot, notebookEvalErr := filepath.EvalSymlinks(notebookRoot)
		if dataEvalErr != nil || notebookEvalErr != nil ||
			!gulu.File.IsSubPath(resolvedDataDir, resolvedNotebookRoot) ||
			!gulu.File.IsSubPath(resolvedNotebookRoot, resolvedRoot) {
			err = fmt.Errorf("notebook asset path resolves outside notebook directory: %s", assetPath)
			return
		}
	}
	resolvedPath, evalErr := filepath.EvalSymlinks(absPath)
	if evalErr != nil {
		err = fmt.Errorf("resolve asset [%s] failed: %w", absPath, evalErr)
		return
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Check whether the assets root printed in [%s] actually exists on disk: `ls -la` on that path and each parent.
  2. If it is a broken symlink, remove it and let SiYuan recreate the directory (or restore from backup).
  3. Fix ownership/permissions so the kernel process can traverse every component (chmod +x on parent dirs, chown to the running user).
  4. If the assets directory legitimately should not exist for this notebook, do not call ResolveDataAssetPath for it; gate the call on existence first.
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the assets root exists and is resolvable before resolving.
assetRoot := filepath.Join(util.DataDir, "assets") // or <box>/assets
if _, err := filepath.EvalSymlinks(assetRoot); err != nil {
    return fmt.Errorf("assets root unavailable, refusing to resolve: %w", err)
}

Try / catch

if _, _, err := model.ResolveDataAssetPath(p); err != nil {
    if strings.Contains(err.Error(), "resolve assets directory") {
        // surface a user-facing 'assets folder missing' message rather than a raw stack
    }
    return err
}

Prevention

When it happens

Trigger: Calling ResolveDataAssetPath with a path whose `assets` directory component does not exist on disk (e.g. the notebook's assets folder was deleted or never created), or where the assets directory is a dangling symlink, or where a parent directory has restrictive permissions (0o000 / owned by another user).

Common situations: Workspace was copied/moved between machines and symlinks broke; a notebook directory was partially deleted leaving a conf.json but no assets/ dir; running the kernel under a different OS user than the one that owns DataDir; backup restore that recreated files but not directory symlinks.

Related errors


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