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
- Verify util.DataDir is configured to live inside util.WorkspaceDir (this is the supported layout).
- Check for and remove any symlink on the workspace or data directory that points outside WorkspaceDir.
- Restart the kernel with a clean --workspace so DataDir is recomputed under the workspace.
- 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
- Keep DataDir inside WorkspaceDir (the only supported layout).
- Do not symlink the workspace or data dir to external locations.
- Run a clean --workspace if the layout was changed.
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
- Query notebook failed
- path is not a child of assets directory: %s
- resolve assets directory [%s] failed: %w
- asset path resolves outside assets directory: %s
- symlink [%s] resolves outside workspace: [%s]
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/353fc09ca2dfcf24.
Report an issue: GitHub.