{"record":{"id":"fb08fa9d68651240","repo":"siyuan-note/siyuan","slug":"block-not-found-or-its-encrypted-notebook-is-locke","errorCode":null,"errorMessage":"block not found or its encrypted notebook is locked","messagePattern":"block not found or its encrypted notebook is locked","errorType":"http","errorClass":null,"httpStatus":200,"severity":"error","filePath":"kernel/api/box_lease.go","lineNumber":71,"sourceCode":"}\n\n// holdEncryptedBlockRequests 按块的实际归属取得响应租约，覆盖省略笔记本参数的通用和批量读取。\nfunc holdEncryptedBlockRequests(c *gin.Context, boxID string, ids []string, allowMissing bool) error {\n\tif boxID != \"\" {\n\t\treturn holdEncryptedBoxRequest(c, boxID)\n\t}\n\tboxIDSet := map[string]struct{}{}\n\tfor _, id := range ids {\n\t\tif !ast.IsNodeIDPattern(id) {\n\t\t\tcontinue\n\t\t}\n\t\tblock := treenode.GetBlockTree(id)\n\t\tif block == nil {\n\t\t\tif allowMissing {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// 查不到的块直接拒绝，防止随后解锁的笔记本在第二次查找时被无租约访问。\n\t\t\treturn errors.New(\"block not found or its encrypted notebook is locked\")\n\t\t}\n\t\tif model.IsEncryptedBox(block.BoxID) {\n\t\t\tboxIDSet[block.BoxID] = struct{}{}\n\t\t}\n\t}\n\tboxIDs := make([]string, 0, len(boxIDSet))\n\tfor id := range boxIDSet {\n\t\tboxIDs = append(boxIDs, id)\n\t}\n\tsort.Strings(boxIDs)\n\tfor _, id := range boxIDs {\n\t\tif err := holdEncryptedBoxRequest(c, id); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/api/box_lease.go#L53-L89","documentation":"holdEncryptedBlockRequests resolves each block ID via treenode.GetBlockTree(id); when a block cannot be resolved and allowMissing is false, it rejects the whole request with this message. The ID either does not exist at all, or it belongs to an encrypted notebook that is currently locked (so the block index is unavailable), and proceeding without a lease would allow un-leased access to that box.","triggerScenarios":"holdBlockRequest processing a batch of block IDs where at least one ID is not in blocktree (deleted block, bad ID) or lives in a locked encrypted notebook, and the call does not set allowMissing.","commonSituations":"Batch API request contains one stale ID from a synced deletion; an encrypted notebook is locked while other requests in the batch target unlocked notebooks; ID typo or truncated ID in a plugin; blocktree not yet built for a newly opened box.","solutions":["Unlock the encrypted notebook that owns the referenced block, then retry","Validate every block ID exists (getBlockInfo) before sending the batch; drop unknown IDs if your caller supports allowMissing semantics","Refresh stale IDs from current document state after sync deletions","Correct ID formatting/typos (22-char IDs, no whitespace)"],"exampleFix":"// before\nawait api.setBlockAttrs(ids); // ids includes a deleted one\n// after\nconst valid = [];\nfor (const id of ids) {\n  if ((await fetchSyncPost(\"/api/block/getBlockInfo\", { id })).code === 0) valid.push(id);\n}\nawait api.setBlockAttrs(valid);","handlingStrategy":"validation","validationCode":"for (const id of ids) {\n  const r = await fetchSyncPost(\"/api/block/getBlockInfo\", { id });\n  if (r.code !== 0) throw new Error(`Unknown or locked block: ${id}`);\n}","typeGuard":null,"tryCatchPattern":"try { await batchApi(ids); } catch (e) { if (String(e).includes(\"encrypted notebook is locked\")) { await promptUnlock(); retry(validIds); } }","preventionTips":["Pre-validate every ID in batches","Unlock all referenced encrypted notebooks first","Prune stale IDs after sync-deletion push events"],"tags":["go","encryption","block-not-found","notebook-lease"],"backgroundTag":"record-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}