{"record":{"id":"7e4b3a0fbafc5fda","repo":"siyuan-note/siyuan","slug":"path-is-not-under-an-assets-directory-s","errorCode":null,"errorMessage":"path is not under an assets directory: %s","messagePattern":"path is not under an assets directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1070,"sourceCode":"\t\t\tif parts[i] == \"assets\" {\n\t\t\t\tassetDirIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif assetDirIndex > 0 {\n\t\t\tboxConfPath := filepath.Join(util.DataDir, parts[0], \".siyuan\", \"conf.json\")\n\t\t\tif !filelock.IsExist(boxConfPath) {\n\t\t\t\terr = fmt.Errorf(\"asset path does not belong to a notebook: %s\", assetPath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif IsEncryptedBox(parts[0]) {\n\t\t\t\terr = fmt.Errorf(\"accessing assets in encrypted notebook [%s] is not supported\", parts[0])\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tif assetDirIndex < 0 {\n\t\terr = fmt.Errorf(\"path is not under an assets directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tassetRootParts := parts[:assetDirIndex+1]\n\tassetRoot := filepath.Join(util.DataDir, filepath.FromSlash(strings.Join(assetRootParts, \"/\")))\n\tif !gulu.File.IsSubPath(assetRoot, absPath) {\n\t\terr = fmt.Errorf(\"path is not a child of assets directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tresolvedRoot, evalErr := ResolveAssetPathWithMissingLeaf(assetRoot)\n\tif evalErr != nil {\n\t\terr = fmt.Errorf(\"resolve assets directory [%s] failed: %w\", assetRoot, evalErr)\n\t\treturn\n\t}\n\tif assetDirIndex > 0 {\n\t\tnotebookRoot := filepath.Join(util.DataDir, parts[0])\n\t\tresolvedDataDir, dataEvalErr := ResolveRealPath(util.DataDir)","sourceCodeStart":1052,"sourceCodeEnd":1088,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1052-L1088","documentation":"ResolveDataAssetPath only accepts asset paths that live under a directory literally named `assets` — either the global `assets/` at the top of the data dir, or `<notebookID>/assets/...` inside a notebook. After sanitizing the relative path, it searches the path segments for an `assets` segment in a valid position; if none is found (assetDirIndex stays -1), this error is thrown.","triggerScenarios":"Calling ResolveDataAssetPath with a path that contains no `assets` directory segment in a valid position, e.g. `20240101120000-abc123/foo.png` (file directly in notebook dir), `somefile.png`, or a path where `assets` appears only as the last segment (`nb/assets` with no leaf) or nested after a non-ID first segment.","commonSituations":"Hand-built asset paths missing the `assets/` directory; referencing files stored outside any assets folder (e.g. custom data files in the notebook root); typos like `asset/` instead of `assets/`; passing document paths instead of asset paths.","solutions":["Place or move the file under the workspace-level `assets/` directory and reference it as `assets/<file>`","Place it under the notebook's own assets directory: `<notebookID>/assets/<file>`","Fix the typo in the path so an `assets` segment appears between the notebook ID (or nothing) and the file name","If you are resolving non-asset files, use a different resolver — ResolveDataAssetPath is only for assets"],"exampleFix":"// before\nrel, abs, err := model.ResolveDataAssetPath(\"20240101120000-abc123/pic.png\")\n// after\nrel, abs, err := model.ResolveDataAssetPath(\"20240101120000-abc123/assets/pic.png\")","handlingStrategy":"validation","validationCode":"func isAssetPath(p string) bool {\n    p = path.Clean(strings.ReplaceAll(p, \"\\\\\", \"/\"))\n    parts := strings.Split(p, \"/\")\n    if len(parts) > 1 && parts[0] == \"assets\" {\n        return true\n    }\n    for i := 1; i < len(parts)-1; i++ {\n        if parts[i] == \"assets\" {\n            return true\n        }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"rel, abs, err := model.ResolveDataAssetPath(assetPath)\nif err != nil {\n    if strings.Contains(err.Error(), \"not under an assets directory\") {\n        return fmt.Errorf(\"%q must be stored under assets/: %w\", assetPath, err)\n    }\n    return err\n}","preventionTips":["Always store programmatic assets under `assets/` or `<notebookID>/assets/`","Normalize with path.Clean and forward slashes before passing asset paths","When generating document paths, insert the assets directory rather than writing files next to .sy files","Use the asset-upload API instead of hand-building asset file paths"],"tags":["go","filesystem","path","assets"],"backgroundTag":"invalid-argument-value","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}