siyuan-note/siyuan · error
encrypted box db not opened for box
Error message
encrypted box db not opened for box
What it means
Thrown by sql.CheckReadonlyStatementInBox when the target notebook is configured as encrypted but its per-box SQLCipher database connection has not been opened yet. SiYuan keeps encrypted notebooks in separate SQLCipher databases registered in a process map (encryptedDBs); until UnlockBox calls sql.OpenEncryptedDB(boxID, dek), GetEncryptedDB returns nil and the validator refuses to run rather than silently validating the statement against the wrong (global siyuan.db) 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 errView on GitHub (pinned to afa823b6b4)
Solutions
- Unlock the encrypted notebook in the SiYuan UI first (UnlockBox opens the SQLCipher db via sql.OpenEncryptedDB), then re-issue the SQL query
- Verify the boxID with /api/notebook/lsbook and confirm the box really is the one you want; a mistyped ID pointing at a locked encrypted box triggers this
- If automating via API/MCP, perform the unlock step for encrypted boxes before issuing any box-scoped SQL
- Query only non-encrypted boxes with raw SQL, or use search APIs that do not require an open box db connection
Example fix
// before
err := sql.CheckReadonlyStatementInBox(stmt, boxID)
// after
if sql.GetEncryptedDB(boxID) == nil && sql.IsEncryptedBoxFn != nil && sql.IsEncryptedBoxFn(boxID) {
// unlock the box first: UnlockBox derives the dek and calls sql.OpenEncryptedDB(boxID, dek)
return fmt.Errorf("box %s is locked; unlock it before querying", boxID)
}
err := sql.CheckReadonlyStatementInBox(stmt, boxID) Defensive patterns
Strategy: validation
Validate before calling
if sql.IsEncryptedBoxFn != nil && sql.IsEncryptedBoxFn(boxID) && sql.GetEncryptedDB(boxID) == nil {
return fmt.Errorf("encrypted box %s is locked; unlock it before running SQL", boxID)
}
err := sql.CheckReadonlyStatementInBox(stmt, boxID) Try / catch
err := sql.CheckReadonlyStatementInBox(stmt, boxID)
if err != nil {
if strings.HasPrefix(err.Error(), "encrypted box db not opened") {
// unlock the box, then retry once
}
} Prevention
- Unlock encrypted notebooks before running any box-scoped SQL automation
- Treat GetEncryptedDB(boxID) == nil on an encrypted box as a precondition failure, not a query failure
- In test code, register a test db via encryptedDBs.Store and stub IsEncryptedBoxFn like kernel/sql/stmt_validate_test.go does
When it happens
Trigger: Calling a SQL API that routes by boxID while the box is encrypted but locked: /api/query with a stmt referencing the encrypted box (kernel/api/sql.go:70), /api/search with box-filtered SQL (kernel/api/search.go:446), MCP execute-sql with boxID (kernel/mcp/tools/sql.go:81), or document statistics (kernel/model/document_stat.go:151). IsEncryptedBoxFn(boxID) returns true but GetEncryptedDB(boxID) is nil, i.e. the box was never unlocked in this kernel session.
Common situations: Kernel restarted so previously unlocked encrypted boxes are locked again; automation scripts (CLI/MCP) run against a workspace before unlocking the notebook; workspace synced to a new device and SQL jobs run before first unlock; a wrong boxID that happens to belong to an encrypted box.
Related errors
- Related operations are being processed, please try again lat
- Encrypted notebooks do not support this operation
- Please unlock the encrypted notebook first
- path belongs to encrypted notebook [%s]: %s
- CLI does not support encrypted notebook [%s]
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/780096015262a7da.
Report an issue: GitHub.