siyuan-note/siyuan · error
invalid block ID [ ]
Error message
invalid block ID [%s]
What it means
buildBlockUpdateOperations validates every input's ID against ast.IsNodeIDPattern before parsing its data; an ID that does not match the SiYuan node ID format aborts the whole batch with "invalid block ID [%s]". IDs are updatable-14 style identifiers and the kernel refuses to operate on anything else.
Solutions
- Use real IDs returned by the kernel (getBlockInfo, query/sql, editor state) — never generate them client-side.
- Validate the ID format before sending (see validation code below).
- Trim whitespace and strip quotes/brackets from IDs parsed out of logs or DOM attributes.
Example fix
// before
await updateBlock({ id: ' 20240101120000-abc1234 ', data: md });
// after
const id = rawId.trim();
if (!/^[0-9]{14}-[a-z0-9]{7}$/.test(id)) throw new Error(`invalid block id: ${rawId}`);
await updateBlock({ id, data: md }); Defensive patterns
Strategy: validation
Validate before calling
function isValidBlockId(id) {
return typeof id === 'string' && /^[0-9]{14}-[a-z0-9]{7}$/.test(id.trim());
} Type guard
function asBlockId(v) {
return typeof v === 'string' && /^[0-9]{14}-[a-z0-9]{7}$/.test(v.trim()) ? v.trim() : null;
} Try / catch
try { await updateBlocks(inputs); } catch (e) { if (String(e.msg).includes('invalid block ID')) console.error('fix id source; ids must come from the kernel'); throw e; } Prevention
- Never fabricate or derive block IDs client-side; read them from API responses or data-id attributes.
- Trim and strip quotes/brackets when extracting IDs from logs or DOM.
- Validate ID format before adding to a batch.
When it happens
Trigger: Passing a hand-composed, truncated, whitespace-padded, or empty string as a block ID in a BlockUpdateInput; sending DOM-derived ids that were mangled or placeholder values like "new-block".
Common situations: Scripts generating IDs themselves instead of reading them from the kernel; ids copied with surrounding quotes/whitespace from logs; tests using obviously fake identifiers.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- block [ ] is not a document that can declare a child…
- block [ ] not found
- block [ ] type is locked: expected , got
- Conf.Language(106)
- Conf.Language(112)
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/ca77a9ce1862fcd4.
Report an issue: GitHub.
Appendix: 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 9f775e8a12)