siyuan-note/siyuan · warning
invalid block ID [%s]
Error message
invalid block ID [%s]
What it means
Returned by buildBlockUpdateOperations for any input whose ID fails ast.IsNodeIDPattern. SiYuan block IDs follow a fixed pattern (20-char ASCII alphanumeric); this guard rejects malformed, empty, or non-block identifiers before any tree work happens.
Source
Thrown at kernel/model/block_update.go:93
transaction := &Transaction{DoOperations: operations}
if err = performTxSyncLocked(transaction); err != nil {
return nil, nil, err
}
return []*Transaction{transaction}, rootIDs, nil
}
func buildBlockUpdateOperations(inputs []BlockUpdateInput, resolveTree blockUpdateTreeResolver, loadTree blockUpdateTreeLoader) (operations []*Operation, rootIDs []string, err error) {
if 1 > len(inputs) {
return nil, nil, errors.New("block updates are empty")
}
luteEngine := util.NewLute()
rootIDSet := map[string]struct{}{}
treeCache := map[blockUpdateTreeKey]*parse.Tree{}
for _, input := range inputs {
if !ast.IsNodeIDPattern(input.ID) {
return nil, nil, fmt.Errorf("invalid block ID [%s]", input.ID)
}
data, dataTree, parseErr := parseBlockUpdateData(input.Data, input.DataType, luteEngine)
if parseErr != nil {
return nil, nil, parseErr
}
var oldTree *parse.Tree
var cacheKey blockUpdateTreeKey
hasCacheKey := false
if blockTree := resolveTree(input.ID); nil != blockTree {
cacheKey = blockUpdateTreeKey{boxID: blockTree.BoxID, rootID: blockTree.RootID}
hasCacheKey = true
oldTree = treeCache[cacheKey]
}
if nil == oldTree {
var loadErr error
oldTree, loadErr = loadTree(input.ID)View on GitHub (pinned to 251596fc0d)
Solutions
- Run ast.IsNodeIDPattern(id) on every input before calling the API and drop invalid entries.
- Trim whitespace and verify the ID length matches the expected block ID length.
- Confirm the ID comes from a blocks table query, not from a different namespace.
Example fix
// before
for _, in := range inputs {
buildBlockUpdateOperations(...)
}
// after
for _, in := range inputs {
if !ast.IsNodeIDPattern(in.ID) {
return fmt.Errorf("skip invalid id %q", in.ID)
}
} Defensive patterns
Strategy: validation
Validate before calling
for _, in := range inputs {
if !ast.IsNodeIDPattern(in.ID) {
return fmt.Errorf("invalid block ID %q", in.ID)
}
} Type guard
func isValidBlockID(id string) bool {
return ast.IsNodeIDPattern(id)
} Prevention
- Source block IDs only from the blocks table or editor selection, never hand-written.
- Trim whitespace from copied IDs before submission.
- Run IsNodeIDPattern on every ID before calling the update API.
When it happens
Trigger: Passing a path, URL, or document ID instead of a block ID; passing an empty string; truncated or whitespace-padded ID; ID generated by something other than the SiYuan ID generator.
Common situations: Client confusing docID/rootID with blockID; copy-paste introducing whitespace; external integration using its own ID scheme.
Related errors
- found invalid ID [%s]
- invalid relation block ID [%s]
- block updates are empty
- unsupported block data type [%s]
- list block has no list item
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/ca77a9ce1862fcd4.
Report an issue: GitHub.