{"record":{"id":"9ab49abfc0b9ff62","repo":"siyuan-note/siyuan","slug":"box-mismatch-caller-specified-s-but-url-has","errorCode":null,"errorMessage":"box mismatch: caller specified [%s] but URL has [%s]","messagePattern":"box mismatch: caller specified \\[(.+?)\\] but URL has \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1202,"sourceCode":"func IsHTMLAssetIFrameSrc(assetPath string) bool {\n\tparsed, err := url.Parse(assetPath)\n\tif err != nil || !strings.EqualFold(parsed.Query().Get(\"iframe\"), \"true\") {\n\t\treturn false\n\t}\n\treturn IsLocalHTMLAssetPath(assetPath)\n}\n\nfunc assetPathAndBox(relativePath, defaultBoxID string) (cleanPath, boxID string, err error) {\n\trelativePath = strings.TrimSpace(relativePath)\n\tboxID = defaultBoxID\n\tif idx := strings.Index(relativePath, \"?\"); idx >= 0 {\n\t\tquery := relativePath[idx+1:]\n\t\trelativePath = relativePath[:idx]\n\t\tif values, parseErr := url.ParseQuery(query); parseErr == nil {\n\t\t\tif queryBoxID := strings.TrimSpace(values.Get(\"box\")); queryBoxID != \"\" {\n\t\t\t\tif defaultBoxID != \"\" && defaultBoxID != queryBoxID {\n\t\t\t\t\t// 调用方指定了 boxID 但 URL 里是另一个 box：拒绝，防止解析到错误 box\n\t\t\t\t\terr = fmt.Errorf(\"box mismatch: caller specified [%s] but URL has [%s]\", defaultBoxID, queryBoxID)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tboxID = queryBoxID\n\t\t\t}\n\t\t}\n\t}\n\tcleanPath = filepath.ToSlash(relativePath)\n\treturn\n}\n\n// GetAssetAbsPathInBox 在指定 box 内解析资源绝对路径，不进行全局遍历。\n// relativePath 必须以 assets/ 前缀开头，boxID 为空且路径没有 box 查询参数时只解析普通/全局资源，不遍历加密 box。\n// 加密 box 直接从 <boxID>/assets/ 查找，不依赖后缀匹配。\nfunc GetAssetAbsPathInBox(relativePath, boxID string) (string, error) {\n\tvar err error\n\trelativePath, boxID, err = assetPathAndBox(relativePath, boxID)\n\tif err != nil {\n\t\treturn \"\", err","sourceCodeStart":1184,"sourceCodeEnd":1220,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1184-L1220","documentation":"assetPathAndBox parses an optional ?box=<id> query parameter from an asset path and reconciles it with the boxID the caller passed explicitly. When the caller supplied a non-empty boxID that differs from the box ID in the URL query, this error is returned to avoid silently resolving the asset from the wrong notebook.","triggerScenarios":"Calling GetAssetAbsPathInBox (or functions routed through assetPathAndBox such as assetReferenceExists, AcquireExportArtifactLease, GetMobileExportName) with e.g. path=\"assets/img.png?box=20240101111111-aaaa\" while also passing boxID=\"20250101111222-bbbb\".","commonSituations":"Copying an asset URL (which embeds its source box) from one document into code that resolves it within a different notebook's context; export code passing the current box while the stored link was created in another notebook; stale hard-coded URLs after merging notebooks.","solutions":["Make the box IDs consistent: either drop the caller-provided boxID (pass \"\") and let the URL's ?box= parameter win, or strip the ?box= query from the path before calling","Update the stored asset link so its ?box= parameter matches the notebook it lives in","If the asset truly belongs to another notebook, call the resolver with that notebook's boxID and no conflicting query parameter"],"exampleFix":"// before: conflict between caller box and URL query\nmodel.GetAssetAbsPathInBox(\"assets/img.png?box=20240101111111-aaaa\", \"20250101111222-bbbb\")\n// after: strip the query or align the box\nmodel.GetAssetAbsPathInBox(\"assets/img.png\", \"20250101111222-bbbb\")","handlingStrategy":"validation","validationCode":"u, _ := url.Parse(assetRef)\nqBox := u.Query().Get(\"box\")\nif qBox != \"\" && callerBoxID != \"\" && qBox != callerBoxID {\n\t// reconcile before calling: pass one source of truth only\n}","typeGuard":null,"tryCatchPattern":"abs, err := model.GetAssetAbsPathInBox(ref, boxID)\nif err != nil && strings.Contains(err.Error(), \"box mismatch\") {\n\t// strip the query and retry with the caller's box\n\tclean := strings.SplitN(ref, \"?\", 2)[0]\n\tabs, err = model.GetAssetAbsPathInBox(clean, boxID)\n}","preventionTips":["Choose one source of truth for the box: either encode it in the URL or pass it as an argument, not both","When copying asset links between notebooks, rewrite the ?box= parameter","Store raw asset paths without query strings in your own persistence layers"],"tags":["asset-resolution","parameter-conflict","notebook"],"backgroundTag":"conflicting-config-options","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"}