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

  1. Pass a path relative to the data dir that begins with assets/, e.g. assets/image.png.
  2. Move or copy the file into the workspace assets/ directory first.
  3. 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

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


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)