siyuan-note/siyuan · error

[%s] is not sub path of workspace

Error message

[%s] is not sub path of workspace

What it means

Thrown by GetAssetAbsPathInBox (kernel/model/assets.go:1097) in the box-scoped branch: after joining `DataDir/boxID/relativePath` into `p` and confirming the file exists, it checks that `p` is still inside util.WorkspaceDir. This catches cases where DataDir/boxID have been manipulated (e.g. via symlinked workspace roots) such that the lexical join leaves the workspace.

Source

Thrown at kernel/model/assets.go:1097

	relativePath = path.Clean(relativePath)
	if relativePath == "." || strings.HasPrefix(relativePath, "../") || relativePath == ".." || path.IsAbs(relativePath) {
		return "", fmt.Errorf("[%s] is not an asset path", relativePath)
	}
	if !strings.HasPrefix(relativePath, "assets/") {
		return "", fmt.Errorf("[%s] is not an asset path (must start with assets/)", relativePath)
	}
	if boxID != "" && !ast.IsNodeIDPattern(boxID) {
		return "", fmt.Errorf("[%s] is not a box id", boxID)
	}

	if boxID == "" {
		return GetAssetAbsPathWithOpt(relativePath, false)
	}

	p := filepath.Join(util.DataDir, boxID, relativePath)
	if gulu.File.IsExist(p) {
		if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
			return "", fmt.Errorf("[%s] is not sub path of workspace", p)
		}
		// 解析符号链接/目录联接,防止软链接跳出资产根目录
		if realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {
			if !gulu.File.IsSubPath(util.WorkspaceDir, realP) {
				return "", fmt.Errorf("symlink [%s] resolves outside workspace: [%s]", p, realP)
			}
			// 验证解析后的路径仍在 <boxID>/assets/ 或全局 data/assets/ 下
			expectedPrefix := filepath.Join(util.DataDir, "assets")
			if boxID != "" {
				expectedPrefix = filepath.Join(util.DataDir, boxID, "assets")
			}
			if !gulu.File.IsSubPath(expectedPrefix, realP) {
				return "", fmt.Errorf("symlink [%s] resolves outside assets directory: [%s]", p, realP)
			}
		}
		return p, nil
	}
	// 非加密 box 的资源可能回退到全局 data/assets(兼容旧笔记本结构)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Verify util.DataDir is configured to live inside util.WorkspaceDir (this is the supported layout).
  2. Check for and remove any symlink on the workspace or data directory that points outside WorkspaceDir.
  3. Restart the kernel with a clean --workspace so DataDir is recomputed under the workspace.
  4. If you intentionally separate data and workspace, note this is unsupported and will keep tripping the guard.
Defensive patterns

Strategy: validation

Validate before calling

// Assert the supported layout before resolving box assets.
if !gulu.File.IsSubPath(util.WorkspaceDir, util.DataDir) {
    return errors.New("DataDir must live inside WorkspaceDir")
}

Try / catch

if _, err := model.GetAssetAbsPathInBox(ref, box); err != nil && strings.Contains(err.Error(), "is not sub path of workspace") {
    // workspace/data layout misconfiguration; fix --workspace rather than retry
}

Prevention

When it happens

Trigger: Calling GetAssetAbsPathInBox with a valid-looking boxID and `assets/...` path where the resulting real directory lives outside WorkspaceDir — for instance WorkspaceDir is a symlink and DataDir resolves elsewhere, or the workspace was reconfigured so DataDir is no longer under WorkspaceDir.

Common situations: Custom workspace setups where --workspace points at a directory but DataDir was overridden (env/workspace config) to live outside it; misconfigured portable deployments; symlinked workspace root that was later moved.

Related errors


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