siyuan-note/siyuan · error

database block [ ] is already bound

Error message

database block [%s] is already bound

What it means

When applying a new block binding for an attribute view row, the engine first checks that the target block is not already bound to a different row in the same database. A block may be bound to only one row per attribute view; if GetBlockValueByBoundID finds an existing row bound to the desired block ID other than the one being replaced, the operation is rejected.

Solutions

  1. Choose a different (unbound) block, or detach the existing row's binding first
  2. Use the duplicate-item path (state.duplicateItemID) so the engine creates a new row instead of rebinding
  3. Query the attribute view for GetBlockValueByBoundID(blockID) before binding to detect the conflict
  4. After sync/merge conflicts, run index rebuild so bindings are consistent, then re-attempt

Example fix

// before
api.bindBlockAv({avID, blockID: existingBoundBlockID})
// after
const row = av.keyValues.flatMap(kv => kv.values).find(v => v.block && v.block.id === blockID)
if (row) api.unbindBlockAv({avID, rowID: row.blockID})
api.bindBlockAv({avID, blockID})
Defensive patterns

Strategy: validation

Validate before calling

const existing = av.keyValues.flatMap(kv => kv.values)
  .find(v => v.block && v.block.id === blockID);
if (existing && existing.blockID !== targetRowID) {
  throw new Error('block already bound to row ' + existing.blockID);
}

Type guard

function isBlockFree(av, blockID) {
  return !av.keyValues.some(kv => kv.values.some(v => v.block && v.block.id === blockID));
}

Prevention

When it happens

Trigger: Calling replaceAttrViewBlock / binding a block to a database where that block's ID already appears as the Block.ID of another row — e.g. duplicating a block that references a bound database entry, or programmatically binding a block that an earlier operation already bound.

Common situations: Copy-pasting or duplicating blocks that carry database bindings; importing documents where bound block IDs collide; scripts issuing bind operations without checking existing bindings; sync merging two workspaces that bound the same block independently.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_binding_undo.go:141

	}
	state.after = value.Clone()
	if inverse != nil {
		op.attributeViewBinding, inverse.attributeViewBinding = state, state
		inverse.attributeViewBindingUndo = true
	}
	targetItemID := value.BlockID
	if state.duplicateItemID != "" {
		targetItemID = state.duplicateItemID
	}
	op.RetData = map[string]any{"targetItemID": targetItemID, "duplicate": state.duplicateItemID != ""}
	return nil
}

func (tx *Transaction) applyAttributeViewBinding(current *av.AttributeView, value, desired *av.Value,
	state *attributeViewBindingSnapshot) error {
	if !desired.IsDetached {
		if existing := current.GetBlockValueByBoundID(desired.Block.ID); existing != nil && existing.BlockID != value.BlockID {
			return fmt.Errorf("database block [%s] is already bound", desired.Block.ID)
		}
	}
	ids := []string{}
	if !value.IsDetached {
		ids = append(ids, value.Block.ID)
	}
	if !desired.IsDetached && !slices.Contains(ids, desired.Block.ID) {
		ids = append(ids, desired.Block.ID)
	}
	nodes, trees := map[string]*ast.Node{}, map[string]*parse.Tree{}
	for _, id := range ids {
		node, tree, err := getNodeByBlockID(tx, id)
		if err != nil {
			return err
		}
		if node == nil {
			return ErrBlockNotFound
		}

View on GitHub (pinned to 9f775e8a12)