siyuan-note/siyuan · error

encrypted box db not opened for box %s

Error message

encrypted box db not opened for box %s

What it means

queryForBox routes box-scoped queries to the box's own database when the box is encrypted. If the box is marked encrypted (IsEncryptedBoxFn) but its database was never opened (GetEncryptedDB returns nil), the function fails closed rather than falling back to the global database, because that would leak or miss encrypted content.

Source

Thrown at kernel/sql/database.go:1529

	}
	if nil == db {
		return nil
	}
	return db.QueryRow(query, args...)
}

// queryForBox 按 box 路由查询多行。加密笔记本用独立 db,否则用全局 db。boxID 为空走全局。
// 加密笔记本未解锁时返回错误——绝不回退全局库。
func queryForBox(boxID, query string, args ...any) (*sql.Rows, error) {
	query = strings.TrimSpace(query)
	if "" == query {
		return nil, errors.New("statement is empty")
	}
	if boxDB := GetEncryptedDB(boxID); boxDB != nil {
		return boxDB.Query(query, args...)
	}
	if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {
		return nil, errors.New("encrypted box db not opened for box " + boxID)
	}
	if nil == db {
		return nil, errors.New("database is nil")
	}
	return db.Query(query, args...)
}

func queryForBoxContext(ctx context.Context, boxID, query string, args ...any) (*sql.Rows, error) {
	query = strings.TrimSpace(query)
	if "" == query {
		return nil, errors.New("statement is empty")
	}
	if boxDB := GetEncryptedDB(boxID); boxDB != nil {
		return boxDB.QueryContext(ctx, query, args...)
	}
	if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {
		return nil, errors.New("encrypted box db not opened for box " + boxID)
	}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Unlock the encrypted notebook (enter the access key) so its per-box DB is opened before querying.
  2. Verify GetEncryptedDB(boxID) is non-nil for this box before issuing box queries.
  3. Check the ordering of box open/encrypted-DB initialization vs the query call in your code path.

Example fix

// before
blocks := sql.QueryEmptyContentEmbedBlocksInBox(boxID, "") // fails if locked
// after
if sql.IsEncryptedBox(boxID) && sql.GetEncryptedDB(boxID) == nil {
    return fmt.Errorf("box %s is locked; unlock the encrypted notebook first", boxID)
}
blocks := sql.QueryEmptyContentEmbedBlocksInBox(boxID, "")
Defensive patterns

Strategy: type-guard

Validate before calling

if (isEncryptedBox(boxID) && getEncryptedDB(boxID) == null) { throw new Error('encrypted box locked: ' + boxID) }

Type guard

function boxQueryable(boxID) { return !isEncryptedBox(boxID) || getEncryptedDB(boxID) != null }

Try / catch

try { rows = queryForBox(boxID, sql) } catch (e) { if (String(e.message).startsWith('encrypted box db not opened')) { await unlockEncryptedBox(boxID); rows = queryForBox(boxID, sql) } else { throw e } }

Prevention

When it happens

Trigger: Calling QueryEmptyContentEmbedBlocksInBox, QueryRootBlockByConditionInBox, or the name/alias/title/reftext query helpers with a boxID whose encrypted DB was not opened — e.g. encrypted notebook not unlocked, key not entered, or box opened before the encrypted DB was registered.

Common situations: Accessing an encrypted notebook after app restart before entering the access key; embedding-block queries and backlink/ref-text indexing racing ahead of encrypted-db open; API calls targeting a locked encrypted box.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/56587ce0e08c5fa6. Report an issue: GitHub.