siyuan-note/siyuan · error

database entry binding

Error message

database entry binding [%s] changed

What it means

During a database binding replacement (bind/replace block in an attribute view) the transaction verifies the current on-disk row value still matches the snapshot recorded when the operation was queued. If the value is missing or its ID, key ID, detached flag, or bound block ID changed, the transaction aborts to avoid overwriting a concurrent modification.

Solutions

  1. Retry the operation after the conflicting change settles (close other editors/sync, then re-bind)
  2. Reload the attribute view so a fresh snapshot is captured and redo the replace
  3. Recreate the affected database row if its value record is permanently out of sync
  4. If this appears during undo/redo only, report the transaction-log sequence as a bug
Defensive patterns

Strategy: retry

Validate before calling

const current = av.rows.find(r => r.id === expected.blockID);
if (!current || current.valueID !== expected.id || current.keyID !== expected.keyID) {
  refreshAttributeView(avID);
}

Try / catch

try {
  await api.replaceAttrViewBlock(op);
} catch (e) {
  if (/database entry binding .* changed/.test(e.msg)) {
    await reloadAV(avID);
    await api.replaceAttrViewBlock(op);
  }
}

Prevention

When it happens

Trigger: replaceAttributeViewBinding executed after another transaction already changed the row: the block was rebound elsewhere, the row was detached/re-keyed, or the entry was deleted so GetBlockValue(expected.BlockID) returns nil or a value with a different Value.ID/KeyID/Block.ID than the captured expectation.

Common situations: Two windows or a sync job editing the same database concurrently; undo/redo racing with live edits; replaying an operation against an attribute view that was re-indexed or restored from history between queueing and execution.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/7fff2064e4dda7c3. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_binding_undo.go:58

		if !tx.isReplay || state.avID != op.AvID || state.before.BlockID != op.PreviousID {
			return fmt.Errorf("invalid database binding replay")
		}
		current, err := tx.readAttributeViewForMutation(state.avID, state.blockID, state.boxID)
		if err != nil {
			return err
		}
		if state.duplicateItemID != "" {
			op.RetData = map[string]any{"targetItemID": state.duplicateItemID, "duplicate": true}
			return nil
		}
		expected, desired := state.before, state.after
		if op.attributeViewBindingUndo {
			expected, desired = desired, expected
		}
		value := current.GetBlockValue(expected.BlockID)
		if value == nil || value.Block == nil || value.ID != expected.ID || value.KeyID != expected.KeyID ||
			value.IsDetached != expected.IsDetached || value.Block.ID != expected.Block.ID {
			return fmt.Errorf("database entry binding [%s] changed", expected.BlockID)
		}
		if err = tx.applyAttributeViewBinding(current, value, desired, state); err != nil {
			return err
		}
		op.RetData = map[string]any{"targetItemID": value.BlockID, "duplicate": false}
		return nil
	}

	var inverse *Operation
	if tx.fromAPI && !tx.isReplay {
		for _, undo := range tx.UndoOperations {
			if undo.Action == "replaceAttrViewBlock" && undo.AvID == op.AvID &&
				undo.PreviousID == op.PreviousID && undo.attributeViewBinding == nil {
				inverse = undo
				break
			}
		}
	}

View on GitHub (pinned to 9f775e8a12)