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
- Choose a different (unbound) block, or detach the existing row's binding first
- Use the duplicate-item path (state.duplicateItemID) so the engine creates a new row instead of rebinding
- Query the attribute view for GetBlockValueByBoundID(blockID) before binding to detect the conflict
- 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
- Check existing bindings before binding a block to a database
- Use the duplicate-row flow when copying blocks instead of raw rebinding
- After importing documents, scan for duplicate bound block IDs and detach extras
- Resolve sync conflicts before rebinding so duplicate bindings do not accumulate
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
- database binding [ ] already exists
- database entry [ ] has a different binding
- attribute view not found
- cannot remove primary key field
- clone attribute view value
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)