siyuan-note/siyuan · error
encrypted blocktree db not opened for box %s
Error message
encrypted blocktree db not opened for box %s
What it means
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.
Source
Thrown at kernel/treenode/blocktree.go:1076
if _, err = boxDB.Exec(s); err != nil {
return
}
}
if err = cleanupInvalidBlockTrees(boxDB); err != nil {
return
}
return
}
// --- box-scoped wrapper(加密笔记本用独立 db,否则用全局 db)---
// 加密笔记本未解锁(db 未打开)时 fail-closed:绝不回退全局库,避免加密笔记本块树操作污染全局 blocktree.db。
func queryForBox(box, stmt string, args ...any) (*sql.Rows, error) {
if boxDB := getEncryptedBlockTreeDB(box); boxDB != nil {
return boxDB.Query(stmt, args...)
}
if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(box) {
return nil, errors.New("encrypted blocktree db not opened for box " + box)
}
return query(stmt, args...)
}
func queryRowForBox(box, stmt string, args ...any) *sql.Row {
if boxDB := getEncryptedBlockTreeDB(box); boxDB != nil {
return boxDB.QueryRow(stmt, args...)
}
if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(box) {
return nil
}
return queryRow(stmt, args...)
}
func execForBox(box, stmt string, args ...any) (sql.Result, error) {
if boxDB := getEncryptedBlockTreeDB(box); boxDB != nil {
return boxDB.Exec(stmt, args...)
}View on GitHub (pinned to 251596fc0d)
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.
Example fix
// before
blocks := treenode.GetBlockTreesInBox(ids, encryptedBoxID) // -> error wrapped in nil result
// after
if treenode.IsEncryptedBoxFn != nil && treenode.IsEncryptedBoxFn(encryptedBoxID) {
if err := unlockEncryptedBox(encryptedBoxID, pass); err != nil {
return err
}
}
blocks := treenode.GetBlockTreesInBox(ids, encryptedBoxID) Defensive patterns
Strategy: validation
Validate before calling
// Skip or unlock encrypted boxes before issuing box-scoped reads.
if treenode.IsEncryptedBoxFn != nil && treenode.IsEncryptedBoxFn(boxID) {
if treenode.GetEncryptedBlockTreeDB(boxID) == nil {
return nil // or trigger unlock; do not fall back to the global db
}
}
return queryForBox(boxID, stmt, args...) Try / catch
rows, err := queryForBox(box, stmt, args...)
if err != nil {
if strings.Contains(err.Error(), "encrypted blocktree db not opened") {
// unlock and retry once, or skip this box
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- encrypted .sy [%s]: base id [%s] != root id [%s]
- encrypted notebook is locked, please unlock it first
- refuse to write decrypted asset inside workspace
- source is not an encrypted asset
- CLI does not support encrypted notebook [%s]
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/8c397d3b1f0cb7dd.
Report an issue: GitHub.