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

  1. Unlock the encrypted notebook through the kernel's unlock flow so its blocktree db is opened before issuing reads.
  2. Catch this error and skip the box (treat as 'no data available') rather than falling back to the global DB.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/8c397d3b1f0cb7dd. Report an issue: GitHub.