{"record":{"id":"5207dcbac3fa9695","repo":"siyuan-note/siyuan","slug":"box-and-datapath-cannot-be-used-together","errorCode":null,"errorMessage":"box and dataPath cannot be used together","messagePattern":"box and dataPath cannot be used together","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/server/serve.go","lineNumber":986,"sourceCode":"\t\t}\n\t\treturn gulu.File.IsSubPath(filepath.Join(util.DataDir, requestBoxID, \"assets\"), assetAbsPath)\n\t}\n\n\tif model.IsEncryptedAssetPath(assetAbsPath) {\n\t\treturn false\n\t}\n\tdataRelativePath, err := filepath.Rel(util.DataDir, assetAbsPath)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_, validatedAbsPath, err := model.ResolveDataAssetPath(filepath.ToSlash(dataRelativePath))\n\treturn err == nil && filepath.Clean(validatedAbsPath) == filepath.Clean(assetAbsPath)\n}\n\nfunc resolveAssetRequestPath(cleanPath, boxID, dataPath string) (string, error) {\n\tif dataPath != \"\" {\n\t\tif boxID != \"\" {\n\t\t\treturn \"\", errors.New(\"box and dataPath cannot be used together\")\n\t\t}\n\t\tdataRelativePath, assetAbsPath, err := model.ResolveDataAssetPath(dataPath)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tassetPath, _, ok := model.AssetPathFromDataRelativePath(dataRelativePath)\n\t\tif !ok || assetPath != cleanPath {\n\t\t\treturn \"\", fmt.Errorf(\"asset path [%s] does not match data path [%s]\", cleanPath, dataPath)\n\t\t}\n\t\treturn assetAbsPath, nil\n\t}\n\tif boxID != \"\" {\n\t\treturn model.GetAssetAbsPathInBox(cleanPath, boxID)\n\t}\n\treturn model.GetAssetAbsPath(cleanPath)\n}\n\nfunc serveAssets(ginServer *gin.Engine) {","sourceCodeStart":968,"sourceCodeEnd":1004,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/server/serve.go#L968-L1004","documentation":"resolveAssetRequestPath serves /assets/ requests in two mutually exclusive ways: by box ID (resolve within a notebook) or by an explicit data path (resolve relative to the workspace data directory). Passing both at once is ambiguous, so the handler rejects the request outright before doing any file resolution.","triggerScenarios":"An HTTP request to the asset endpoint that supplies both the `box` query parameter and the `dataPath` query parameter; any code path calling resolveAssetRequestPath(cleanPath, boxID, dataPath) with both non-empty strings (kernel/server/serve.go:986).","commonSituations":"A client (script, plugin, third-party WebDAV/HTTP client) built an asset URL by blindly appending every known parameter; a bookmarked URL from an older SiYuan version that used box now also carries dataPath after a frontend change.","solutions":["Remove the `box` query parameter from the request URL and keep only `dataPath`","Or remove `dataPath` and keep only `box` when the asset lives inside a specific notebook","Fix the client code that constructs the URL so it chooses exactly one resolution strategy"],"exampleFix":"// before\nGET /assets/foo.png?box=20240101120000-abc123&dataPath=assets/foo.png\n// after\nGET /assets/foo.png?box=20240101120000-abc123","handlingStrategy":"validation","validationCode":"// build the asset URL with exactly one resolution strategy\nconst url = dataPath\n  ? `/assets/${name}?dataPath=${encodeURIComponent(dataPath)}`\n  : `/assets/${name}?box=${boxID}`;","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(url);\n  if (res.status === 400 && (await res.text()).includes(\"box and dataPath\")) {\n    // strip one param and retry\n  }\n} catch (e) { /* network */ }","preventionTips":["Never append both `box` and `dataPath` query parameters","Centralize asset URL construction in one helper","Prefer dataPath-only URLs when the asset is workspace-wide"],"tags":["http","assets","conflicting-parameters","go"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}