{"record":{"id":"8c397d3b1f0cb7dd","repo":"siyuan-note/siyuan","slug":"encrypted-blocktree-db-not-opened-for-box-s","errorCode":null,"errorMessage":"encrypted blocktree db not opened for box %s","messagePattern":"encrypted blocktree db not opened for box (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/blocktree.go","lineNumber":1076,"sourceCode":"\t\tif _, err = boxDB.Exec(s); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tif err = cleanupInvalidBlockTrees(boxDB); err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\n// --- box-scoped wrapper（加密笔记本用独立 db，否则用全局 db）---\n// 加密笔记本未解锁（db 未打开）时 fail-closed：绝不回退全局库，避免加密笔记本块树操作污染全局 blocktree.db。\n\nfunc queryForBox(box, stmt string, args ...any) (*sql.Rows, error) {\n\tif boxDB := getEncryptedBlockTreeDB(box); boxDB != nil {\n\t\treturn boxDB.Query(stmt, args...)\n\t}\n\tif IsEncryptedBoxFn != nil && IsEncryptedBoxFn(box) {\n\t\treturn nil, errors.New(\"encrypted blocktree db not opened for box \" + box)\n\t}\n\treturn query(stmt, args...)\n}\n\nfunc queryRowForBox(box, stmt string, args ...any) *sql.Row {\n\tif boxDB := getEncryptedBlockTreeDB(box); boxDB != nil {\n\t\treturn boxDB.QueryRow(stmt, args...)\n\t}\n\tif IsEncryptedBoxFn != nil && IsEncryptedBoxFn(box) {\n\t\treturn nil\n\t}\n\treturn queryRow(stmt, args...)\n}\n\nfunc execForBox(box, stmt string, args ...any) (sql.Result, error) {\n\tif boxDB := getEncryptedBlockTreeDB(box); boxDB != nil {\n\t\treturn boxDB.Exec(stmt, args...)\n\t}","sourceCodeStart":1058,"sourceCodeEnd":1094,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/treenode/blocktree.go#L1058-L1094","documentation":"Returned by queryForBox in kernel/treenode/blocktree.go when IsEncryptedBoxFn reports the box is encrypted but getEncryptedBlockTreeDB(box) returned nil — i.e. the notebook is encrypted and has not been unlocked, so its per-box blocktree DB is not open. The wrapper is deliberately fail-closed: it never falls back to the global DB to avoid leaking or polluting encrypted data.","triggerScenarios":"Calling a box-scoped blocktree read (GetBlockTreesInBox, ExistBlockTreesInBox, queryForBox-based helpers) for an encrypted notebook whose db is not currently open. Happens before the user unlocks the notebook or after it is re-locked.","commonSituations":"Sync, search, or index operations that enumerate all notebooks including locked encrypted ones; an API client targeting an encrypted notebook without first unlocking it; the unlock session expired.","solutions":["Unlock the encrypted notebook through the kernel's unlock flow so its blocktree db is opened before issuing reads.","Catch this error and skip the box (treat as 'no data available') rather than falling back to the global DB.","In batch loops over boxes, check IsEncryptedBoxFn(box) and whether getEncryptedBlockTreeDB(box) is non-nil before proceeding."],"exampleFix":"// before\nblocks := treenode.GetBlockTreesInBox(ids, encryptedBoxID) // -> error wrapped in nil result\n\n// after\nif treenode.IsEncryptedBoxFn != nil && treenode.IsEncryptedBoxFn(encryptedBoxID) {\n    if err := unlockEncryptedBox(encryptedBoxID, pass); err != nil {\n        return err\n    }\n}\nblocks := treenode.GetBlockTreesInBox(ids, encryptedBoxID)","handlingStrategy":"validation","validationCode":"// Skip or unlock encrypted boxes before issuing box-scoped reads.\nif treenode.IsEncryptedBoxFn != nil && treenode.IsEncryptedBoxFn(boxID) {\n    if treenode.GetEncryptedBlockTreeDB(boxID) == nil {\n        return nil // or trigger unlock; do not fall back to the global db\n    }\n}\nreturn queryForBox(boxID, stmt, args...)","typeGuard":null,"tryCatchPattern":"rows, err := queryForBox(box, stmt, args...)\nif err != nil {\n    if strings.Contains(err.Error(), \"encrypted blocktree db not opened\") {\n        // unlock and retry once, or skip this box\n    }\n}","preventionTips":["Gate cross-box loops on getEncryptedBlockTreeDB(box) != nil for encrypted boxes.","Unlock encrypted notebooks before any read/write/index operation against them.","Never fall back to the global DB for an encrypted box — that would leak data."],"tags":["encryption","blocktree","security","sqlite","fail-closed"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}