{"record":{"id":"b86d85388c33c2a6","repo":"siyuan-note/siyuan","slug":"conf-language-314-b86d85","errorCode":null,"errorMessage":"Conf.Language(314)","messagePattern":"Conf\\.Language\\(314\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/asset_download_read.go","lineNumber":131,"sourceCode":"\t\t\t}\n\t\t}\n\t\tif lookupPath == relativePath {\n\t\t\treturn absPath, nil\n\t\t}\n\t\tcandidates = append(candidates, absPath)\n\t}\n\tsort.Strings(candidates)\n\tif len(candidates) > 0 {\n\t\treturn candidates[0], nil\n\t}\n\treturn \"\", nil\n}\n\n// ensureReadableAssetLocal 先检查加密笔记本准入，再下载原始密文；实际读取仍须认证解密。\nfunc ensureReadableAssetLocal(absPath string) error {\n\tboxID := ExtractBoxIDFromAssetsPath(absPath)\n\tif boxID != \"\" && IsEncryptedBox(boxID) && !IsBoxUnlocked(boxID) {\n\t\treturn errors.New(Conf.Language(314))\n\t}\n\tif gulu.File.IsSubPath(util.DataDir, absPath) {\n\t\tif err := EnsureAssetPrefixLocal(absPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn EnsureAssetLocal(absPath)\n}\n\n// prepareExportAssets 在导出持有笔记本读锁或生成产物之前补齐文档引用的资源。\nfunc prepareExportAssets(boxID string, docPaths []string, includeFootnotes ...bool) error {\n\tif boxID != \"\" && IsEncryptedBox(boxID) && !IsBoxUnlocked(boxID) {\n\t\treturn errors.New(Conf.Language(314))\n\t}\n\tdeferred, err := DeferredSyncAssets()\n\tif err != nil || len(deferred) == 0 {\n\t\treturn err\n\t}","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/asset_download_read.go#L113-L149","documentation":"ensureReadableAssetLocal enforces that, before any raw ciphertext of an asset is downloaded/made locally readable, the asset's owning notebook, if encrypted, must be unlocked; otherwise it returns the localized Conf.Language(314) message. This ordering guarantee (access check first, download second) means the actual read afterwards still requires authenticated decryption.","triggerScenarios":"readAssetBytesInBox, uploadAssets2Cloud, RenameAsset, or PrepareRichClipboardAssets invoked with absPath inside an encrypted box that is currently locked (fresh start, auto-lock, or lock explicitly engaged).","commonSituations":"Plugin performs clipboard asset preparation or asset rename while the encrypted notebook has auto-locked; a batch asset-upload job runs overnight after the box locked; scripting against the kernel API without unlocking first.","solutions":["Unlock the encrypted notebook (enter passphrase in UI or call the unlock API) and retry","Guard callers with IsBoxUnlocked(ExtractBoxIDFromAssetsPath(absPath)) before invoking these asset APIs","Adjust auto-lock timeout in the encrypted notebook settings if it locks too aggressively for the workflow","Re-enter the passphrase if the unlock attempt itself failed due to a wrong password"],"exampleFix":"// before\nerr := model.RenameAsset(oldPath, newPath) // notebook locked\n// after\nif box := model.ExtractBoxIDFromAssetsPath(oldPath); model.IsEncryptedBox(box) && !model.IsBoxUnlocked(box) {\n    return fmt.Errorf(\"unlock notebook %s first\", box)\n}\nerr := model.RenameAsset(oldPath, newPath)","handlingStrategy":"validation","validationCode":"boxID := model.ExtractBoxIDFromAssetsPath(absPath)\nif boxID != \"\" && model.IsEncryptedBox(boxID) && !model.IsBoxUnlocked(boxID) {\n    return errors.New(\"unlock notebook \" + boxID + \" first\")\n}","typeGuard":null,"tryCatchPattern":"if err := model.RenameAsset(old, new); err != nil && strings.Contains(err.Error(), conf.Conf.Language(314)) {\n    promptUserToUnlock(boxID) // then retry\n    return err\n}","preventionTips":["Check lock state before batch asset operations (rename/upload/clipboard)","Handle auto-lock: re-check IsBoxUnlocked between long batch steps","Unlock all encrypted boxes at the start of automation jobs","Show the localized 314 message to end users rather than raw errors"],"tags":["encryption","locked-notebook","assets"],"backgroundTag":"authentication-required","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"}