siyuan-note/siyuan · error

encrypted notebook is locked, please unlock it first

Error message

encrypted notebook is locked, please unlock it first

What it means

Thrown by holdAVBoxReadLock when an Attribute View belongs to an encrypted notebook whose DEK is no longer cached (the box was locked, or the auto-lock timer fired). The lock was acquired but AVIsBoxUnlocked then returned false, so reading the AV — which requires the DEK to decrypt — is refused to avoid returning ciphertext or stale plaintext.

Source

Thrown at kernel/av/encrypted_hook.go:58

// AVIsBoxUnlocked 由 model 层注入,判断加密笔记本是否仍持有 DEK。
var AVIsBoxUnlocked func(boxID string) bool

// AVGetBlockBoxID 由 model 层注入,返回 blockID 所在的 boxID(查 blocktree)。
// 用于镜像写入时校验源块与 AV 定义是否处于同一加密边界。
var AVGetBlockBoxID func(blockID string) string

func holdAVBoxReadLock(boxID string) (release func(), err error) {
	release = func() {}
	if boxID == "" || AVIsEncryptedBox == nil || !AVIsEncryptedBox(boxID) {
		return
	}
	if AVLockAcquire == nil || AVLockRelease == nil || AVIsBoxUnlocked == nil {
		return nil, errors.New("encrypted notebook lock callbacks are not initialized")
	}
	AVLockAcquire(boxID)
	if !AVIsBoxUnlocked(boxID) {
		AVLockRelease(boxID)
		return nil, errors.New("encrypted notebook is locked, please unlock it first")
	}
	return func() {
		AVLockRelease(boxID)
	}, nil
}

// pendingAVBox 记录首次创建的 AV 归属哪个加密 box。
// handler 层创建 AV 前调 SetAVBoxID(avID, boxID),SaveAttributeView 时
// findAttributeViewPath 会先查 pending 映射,找到则写入对应加密笔记本路径。
var pendingAVBox = map[string]string{}
var pendingAVBoxLock = sync.RWMutex{}

// SetAVBoxID 预设 AV 定义的归属 box。加密笔记本创建 AV 时调用,boxID 为空时清理映射。
// 普通笔记本不需要调(AV 默认走全局路径)。
func SetAVBoxID(avID, boxID string) {
	pendingAVBoxLock.Lock()
	defer pendingAVBoxLock.Unlock()
	if boxID != "" {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Unlock the notebook in the UI, then retry the operation (the kernel re-caches the DEK).
  2. Raise or disable the auto-lock timeout if this recurs during long exports.
  3. Avoid concurrent lock-and-read: pause sync/export before locking a notebook that owns AVs.
Defensive patterns

Strategy: validation

Validate before calling

// Before performing an AV read on a possibly-encrypted box, check unlock state.
func canReadAV(boxID string) bool {
    if av.AVIsEncryptedBox == nil || !av.AVIsEncryptedBox(boxID) {
        return true // plain notebook
    }
    if av.AVIsBoxUnlocked == nil {
        return false // encrypted but callbacks not ready
    }
    return av.AVIsBoxUnlocked(boxID)
}

Try / catch

// Treat a lock error as a recoverable precondition failure.
release, err := av.HoldAVBoxReadLock(boxID) // exported wrapper if available
if err != nil {
    // prompt the user to unlock, then retry the original operation
    return err
}
defer release()

Prevention

When it happens

Trigger: Any AV read on an encrypted box races with a lock event. Concrete call sites: parseAttributeViewByPathInBox (av.go:788) and loadAttributeViewSearchInfoInBox (av.go:631), both invoked while the editor, search index, or sync is touching an AV inside a notebook the user just locked.

Common situations: User clicks 'Lock notebook' (or auto-lock trips) while a database view is open or being indexed; sync triggers re-encryption mid-read; long-running export touches encrypted AVs after the session expired.

Related errors


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