siyuan-note/siyuan · error

encrypted box db not opened for box {boxID}

Error message

encrypted box db not opened for box {boxID}

What it means

CheckReadonlyStatementInBox returns this when IsEncryptedBoxFn reports the notebook is encrypted but GetEncryptedDB(boxID) returned nil — meaning the encrypted notebook's database has not been unlocked/opened yet. The kernel refuses to run SQL against an encrypted box whose key is not loaded.

Source

Thrown at kernel/sql/stmt_validate.go:181

// 注意:若字符串里在语法上还有第二条及以后的语句,本函数只针对「首条」对应的 stmt 做判断,
// 不会拒绝多语句。与 CheckSingleStatement 组合即可得到「单条 + 只读」策略。
// 仅允许 SELECT 和 WITH 查询,避免 SQLite 将 ATTACH、DETACH 和事务控制语句标记为只读后放行。
func CheckReadonlyStatement(stmt string) error {
	return checkReadonlyStatement(stmt, db)
}

// CheckAssetContentReadonlyStatement 在资源文件内容数据库连接上检查 SQL 是否只读。
func CheckAssetContentReadonlyStatement(stmt string) error {
	return checkReadonlyStatement(stmt, assetContentDB)
}

// CheckReadonlyStatementInBox 在指定笔记本对应的数据库连接上检查 SQL 是否只读。
func CheckReadonlyStatementInBox(stmt, boxID string) error {
	targetDB := db
	if boxDB := GetEncryptedDB(boxID); nil != boxDB {
		targetDB = boxDB
	} else if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {
		return errors.New("encrypted box db not opened for box " + boxID)
	}
	return checkReadonlyStatement(stmt, targetDB)
}

func checkReadonlyStatement(stmt string, targetDB *sql.DB) error {
	if strings.TrimSpace(stmt) == "" {
		return errors.New("SQL statement is empty")
	}
	if !isReadonlyQueryStatement(stmt) {
		return errors.New("SQL statement is not a read-only query")
	}
	if nil == targetDB {
		return errors.New("database is nil")
	}
	ctx := context.Background()
	conn, err := targetDB.Conn(ctx)
	if err != nil {
		return err

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Unlock the encrypted notebook in the UI (enter its passphrase) before querying, so GetEncryptedDB returns a live connection.
  2. If scripting, perform the unlock step (set box password / open box) before issuing the SQL query.
  3. Verify the boxID is correct and that the box is actually encrypted (otherwise a wrong ID can mismatch).

Example fix

// before: query encrypted box without unlocking
api.QuerySQLBox("SELECT * FROM blocks", encryptedBoxID)
// after: unlock first, then query
api.UnlockBox(encryptedBoxID, passphrase)
api.QuerySQLBox("SELECT * FROM blocks", encryptedBoxID)
Defensive patterns

Strategy: validation

Validate before calling

// Caller side: ensure the encrypted box is unlocked before querying.
async function ensureBoxUnlocked(boxID: string, passphrase: string): Promise<void> {
  const opened = await api.isEncryptedBoxOpened(boxID)
  if (!opened) await api.openEncryptedBox(boxID, passphrase)
}

Try / catch

try { return await api.querySQLInBox(stmt, boxID) }
catch (e) {
  if (/encrypted box db not opened/i.test(String(e))) {
    await api.openEncryptedBox(boxID, passphrase)
    return api.querySQLInBox(stmt, boxID)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling the per-box SQL query API with a boxID whose notebook is encrypted but the user has not unlocked it (no password provided / box not yet opened in this session).

Common situations: Encrypted notebook exists but kernel just started and user hasn't entered the passphrase; passphrase rejected so the DB stayed closed; automation/script targets an encrypted box without performing unlock first.

Related errors


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