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

CheckReadonlyStatementInBox validates a SQL statement on the database connection belonging to a specific notebook (box). If the box is marked encrypted but its per-box SQLite database handle has not been opened (GetEncryptedDB returned nil), validation cannot proceed safely and the function fails with this error instead of falling back to the main (non-encrypted) connection.

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 8641553a1f)

Solutions

  1. Ensure the encrypted box is unlocked/opened first so GetEncryptedDB(boxID) returns a handle before validating SQL
  2. Re-trigger opening of the box DB (reload the notebook) and retry the query
  3. Verify the box ID is correct and the notebook is actually loaded in this workspace
  4. If you control the code, check GetEncryptedDB(boxID) != nil (or skip validation for closed boxes) before calling CheckReadonlyStatementInBox

Example fix

// before
if err := sql.CheckReadonlyStatementInBox(stmt, boxID); err != nil { ... }
// after
if sql.GetEncryptedDB(boxID) == nil {
	return fmt.Errorf("box %s is not open yet", boxID)
}
if err := sql.CheckReadonlyStatementInBox(boxID, stmt); err != nil { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Go: only validate when the encrypted box DB is actually open
if sql.GetEncryptedDB(boxID) == nil {
	return errors.New("encrypted box not open: " + boxID)
}
err := sql.CheckReadonlyStatementInBox(stmt, boxID)

Type guard

func encryptedBoxReady(boxID string) bool { return sql.GetEncryptedDB(boxID) != nil }

Try / catch

if err := sql.CheckReadonlyStatementInBox(stmt, boxID); err != nil {
	if strings.HasPrefix(err.Error(), "encrypted box db not opened") {
		// prompt unlock / reopen the notebook, then retry
	}
}

Prevention

When it happens

Trigger: Calling CheckReadonlyStatementInBox(stmt, boxID) where IsEncryptedBoxFn(boxID) is true but GetEncryptedDB(boxID) is nil — i.e. the encrypted box's DB was never opened (e.g. before unlock/initialization or after it was closed) (kernel/sql/stmt_validate.go:181).

Common situations: Querying an encrypted notebook before the user unlocked it; the encrypted box DB was closed after an error and not reopened; a race where a query arrives during box load; referencing a box ID for an encrypted notebook whose key material is unavailable.

Related errors


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