{"record":{"id":"de907c0d8146b5dc","repo":"siyuan-note/siyuan","slug":"resolve-assets-root-s-failed-w","errorCode":null,"errorMessage":"resolve assets root [%s] failed: %w","messagePattern":"resolve assets root \\[(.+?)\\] failed: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1154,"sourceCode":"\t\treturn \"\", err\n\t}\n\treturn \"\", fmt.Errorf(Conf.Language(12), relativePath)\n}\n\nfunc getAssetAbsPath(relativePath string, includeEncrypted bool) (absPath string, err error) {\n\trelativePath = filepath.ToSlash(relativePath)\n\t// 在 data 文件夹下搜索，主要是 data/assets 文件夹\n\tp := filepath.Join(util.DataDir, relativePath)\n\tif gulu.File.IsExist(p) {\n\t\tif !gulu.File.IsSubPath(util.WorkspaceDir, p) {\n\t\t\treturn \"\", fmt.Errorf(\"[%s] is not sub path of workspace\", p)\n\t\t}\n\t\t// 解析符号链接，验证真实路径仍在 data/assets/ 下\n\t\tif realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {\n\t\t\tassetsRoot := util.GetDataAssetsAbsPath()\n\t\t\trealAssetsRoot, rootEvalErr := filepath.EvalSymlinks(assetsRoot)\n\t\t\tif rootEvalErr != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"resolve assets root [%s] failed: %w\", assetsRoot, rootEvalErr)\n\t\t\t}\n\t\t\tif !gulu.File.IsSubPath(realAssetsRoot, realP) {\n\t\t\t\treturn \"\", fmt.Errorf(\"symlink [%s] resolves outside data/assets: [%s]\", p, realP)\n\t\t\t}\n\t\t\t// 安全校验使用解析后的路径，返回原路径以便下游与 DataDir 保持同一路径形式\n\t\t\treturn p, nil\n\t\t}\n\t\treturn p, nil\n\t}\n\n\t// 在文档同级 assets 文件夹下搜索\n\tif !strings.HasPrefix(relativePath, \"assets/\") {\n\t\treturn \"\", nil\n\t}\n\tnotebooks, err := ListNotebooks()\n\tif err != nil {\n\t\treturn \"\", errors.New(Conf.Language(0))\n\t}","sourceCodeStart":1136,"sourceCodeEnd":1172,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L1136-L1172","documentation":"Thrown by getAssetAbsPath (kernel/model/assets.go:1154) when filepath.EvalSymlinks fails on util.GetDataAssetsAbsPath() — the global `data/assets` root. EvalSymlinks fails when the assets root directory does not exist, contains a broken link in its path, or is not traversable. It blocks the symlink-escape check from running on an unresolvable root.","triggerScenarios":"Calling the global asset resolver for a global asset that is itself a symlink, at a moment when the `data/assets` directory (or one of its parents) cannot be resolved: missing directory, dangling symlink in the chain, or insufficient permissions.","commonSituations":"The global `data/assets` directory was deleted or never created; a parent of it is a broken symlink after a workspace move; permission change preventing traversal; partial restore.","solutions":["Verify the assets root exists and is traversable: `ls -la <DataDir>/assets` and check each parent.","Recreate the directory if missing (SiYuan recreates it on demand for new uploads).","Fix or remove broken symlinks in the path chain.","Correct ownership/permissions so the kernel process can traverse to the assets root."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify the global assets root is resolvable before resolving a symlinked global asset.\nroot := util.GetDataAssetsAbsPath()\nif _, err := filepath.EvalSymlinks(root); err != nil {\n    return fmt.Errorf(\"global assets root unavailable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if _, err := model.GetAssetAbsPath(ref); err != nil && strings.Contains(err.Error(), \"resolve assets root\") {\n    // global data/assets missing/blocked; recreate or fix perms, then retry\n}","preventionTips":["Keep data/assets on reliable local storage.","Fix ownership so the kernel can traverse to the assets root.","Recreate data/assets if it is deleted (SiYuan recreates it for new uploads)."],"tags":["assets","symlinks","filesystem","permissions","workspace"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}