siyuan-note/siyuan · error
database [ ] moved across notebook encryption boundaries
Error message
database [%s] moved across notebook encryption boundaries
What it means
readAttributeViewForMutation resolves which notebook actually carries the attribute view's anchor block. When the exact carrier is found (resolveAttributeViewCarrierBoxID returned exact=true) but its box ID differs from the boxID passed by the caller, the database block has been moved into a notebook with different encryption settings, and the transaction refuses to mutate it under the stale box context. This protects encrypted-notebook key-envelope boundaries from cross-notebook writes.
Solutions
- Refresh the block's current carrier: re-resolve the block's notebook ID and re-issue the transaction with the correct boxID
- If the move was unintended, move the database block back to its original notebook before applying the mutation
- Ensure the client serializes operations correctly — do not run queued mutations that assumed the pre-move notebook
Example fix
// before
_, err := tx.readAttributeViewForMutation(avID, blockID, oldBoxID)
// after
carrierBox, _, resolveErr := resolveAttributeViewCarrierBoxID(blockID)
if resolveErr == nil {
_, err = tx.readAttributeViewForMutation(avID, blockID, carrierBox)
} Defensive patterns
Strategy: validation
Validate before calling
const carrier = await resolveCarrierBox(blockID);
if (carrier !== txBoxID) {
throw new Error("database moved across notebooks; re-resolve boxID before mutating");
} Try / catch
try {
await api.mutateAttributeView({ avID, blockID, boxID });
} catch (e) {
if (String(e.msg).includes("moved across notebook encryption boundaries")) {
const boxID = await resolveCarrierBox(blockID); // re-resolve and retry
await api.mutateAttributeView({ avID, blockID, boxID });
}
} Prevention
- Re-resolve a block's notebook ID after any move/drag between notebooks before mutating its attribute view
- Don't cache boxID across long-running queues or undo/redo replays
- Be extra careful when notebooks have different encryption settings — operations cannot span those boundaries
When it happens
Trigger: Calling any mutation path that goes through readAttributeViewForMutation (replaceAttributeViewBinding, restoreEmbeddedAttributeViewHistory, flushAttributeViewBlockDeletions, restoreDeletedAttributeViewBlocks, removeAttributeViewField, or inline transaction handlers) with a boxID that no longer matches the block's actual carrier notebook, after the block (e.g. a database block) was dragged or moved between notebooks with different encryption states.
Common situations: A database/embed block was moved across notebooks before a queued transaction ran; replaying an undo/redo or history-restore operation recorded before the move; a client cached the old box ID after a move operation.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Conf.Language(381)
- encrypted attribute view history is missing valid notebook…
- encrypted attribute view in global storage
- encrypted attribute view snapshot has no matching notebook
- encrypted attribute view snapshot is missing notebook…
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/5c531f8ca02bc3f3.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_transaction.go:43
"github.com/siyuan-note/siyuan/kernel/av"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/util"
)
// 事务失败时恢复本次写入涉及的数据库和文档,租约保持到提交或回滚结束。
type attributeViewRollback struct {
views map[string]*av.AttributeView
trees map[string]*parse.Tree
leases map[string]bool
}
func (tx *Transaction) readAttributeViewForMutation(avID, blockID, boxID string) (*av.AttributeView, error) {
carrierBoxID, exact, err := resolveAttributeViewCarrierBoxID(blockID)
if err != nil {
return nil, err
}
if exact && carrierBoxID != boxID {
return nil, fmt.Errorf("database [%s] moved across notebook encryption boundaries", avID)
}
if tx.attributeViewRollback == nil {
tx.attributeViewRollback = &attributeViewRollback{views: map[string]*av.AttributeView{},
trees: map[string]*parse.Tree{}, leases: map[string]bool{}}
}
if boxID != "" && !tx.attributeViewRollback.leases[boxID] {
if err = AcquireEncryptedBoxOperation(boxID); err != nil {
return nil, err
}
tx.attributeViewRollback.leases[boxID] = true
}
current, err := av.ParseAttributeViewForIndexInBox(avID, boxID)
if err == nil && current == nil {
err = av.ErrViewNotFound
}
return current, err
}
View on GitHub (pinned to 9f775e8a12)