siyuan-note/siyuan · error

block not found or its encrypted notebook is locked

Error message

block not found or its encrypted notebook is locked

What it means

holdEncryptedBlockRequests resolves each block ID via treenode.GetBlockTree(id); when a block cannot be resolved and allowMissing is false, it rejects the whole request with this message. The ID either does not exist at all, or it belongs to an encrypted notebook that is currently locked (so the block index is unavailable), and proceeding without a lease would allow un-leased access to that box.

Source

Thrown at kernel/api/box_lease.go:71

}

// holdEncryptedBlockRequests 按块的实际归属取得响应租约,覆盖省略笔记本参数的通用和批量读取。
func holdEncryptedBlockRequests(c *gin.Context, boxID string, ids []string, allowMissing bool) error {
	if boxID != "" {
		return holdEncryptedBoxRequest(c, boxID)
	}
	boxIDSet := map[string]struct{}{}
	for _, id := range ids {
		if !ast.IsNodeIDPattern(id) {
			continue
		}
		block := treenode.GetBlockTree(id)
		if block == nil {
			if allowMissing {
				continue
			}
			// 查不到的块直接拒绝,防止随后解锁的笔记本在第二次查找时被无租约访问。
			return errors.New("block not found or its encrypted notebook is locked")
		}
		if model.IsEncryptedBox(block.BoxID) {
			boxIDSet[block.BoxID] = struct{}{}
		}
	}
	boxIDs := make([]string, 0, len(boxIDSet))
	for id := range boxIDSet {
		boxIDs = append(boxIDs, id)
	}
	sort.Strings(boxIDs)
	for _, id := range boxIDs {
		if err := holdEncryptedBoxRequest(c, id); err != nil {
			return err
		}
	}
	return nil
}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Unlock the encrypted notebook that owns the referenced block, then retry
  2. Validate every block ID exists (getBlockInfo) before sending the batch; drop unknown IDs if your caller supports allowMissing semantics
  3. Refresh stale IDs from current document state after sync deletions
  4. Correct ID formatting/typos (22-char IDs, no whitespace)

Example fix

// before
await api.setBlockAttrs(ids); // ids includes a deleted one
// after
const valid = [];
for (const id of ids) {
  if ((await fetchSyncPost("/api/block/getBlockInfo", { id })).code === 0) valid.push(id);
}
await api.setBlockAttrs(valid);
Defensive patterns

Strategy: validation

Validate before calling

for (const id of ids) {
  const r = await fetchSyncPost("/api/block/getBlockInfo", { id });
  if (r.code !== 0) throw new Error(`Unknown or locked block: ${id}`);
}

Try / catch

try { await batchApi(ids); } catch (e) { if (String(e).includes("encrypted notebook is locked")) { await promptUnlock(); retry(validIds); } }

Prevention

When it happens

Trigger: holdBlockRequest processing a batch of block IDs where at least one ID is not in blocktree (deleted block, bad ID) or lives in a locked encrypted notebook, and the call does not set allowMissing.

Common situations: Batch API request contains one stale ID from a synced deletion; an encrypted notebook is locked while other requests in the batch target unlocked notebooks; ID typo or truncated ID in a plugin; blocktree not yet built for a newly opened box.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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