siyuan-note/siyuan · error
block [ ] not found
Error message
block [%s] not found
What it means
Even after the tree loads successfully, the target node may not exist within it; buildBlockUpdateOperations looks up input.ID with treenode.GetNodeInTree and returns "block [%s] not found" when nil. The batch update is aborted before any operations are created.
Solutions
- Re-fetch current block IDs (/api/query/sql or /api/block/getBlockInfo) and retry only with existing IDs.
- Per-block, verify the ID's root: SELECT root_id FROM blocks WHERE id='<id>' and ensure the update targets that document's current state.
- Catch this error per batch item and skip/queue rather than aborting unrelated updates.
- If the block was deleted, recreate it via insert API rather than update.
Example fix
// before
for (const item of updates) await updateBlock(item.id, item.data);
// after
for (const item of updates) {
const info = await fetchPost('/api/block/getBlockInfo', { id: item.id });
if (info.code === 0) await updateBlock(item.id, item.data);
else console.warn(`skipping missing block ${item.id}`);
} Defensive patterns
Strategy: validation
Validate before calling
async function nodeInCurrentTree(id) {
const res = await fetchPost('/api/block/getBlockInfo', { id });
return res.code === 0;
} Try / catch
try { await updateBlocks(inputs); } catch (e) { if (String(e.msg).match(/block \[.+\] not found/)) { inputs = await revalidateIds(inputs); if (inputs.length) await updateBlocks(inputs); } else throw e; } Prevention
- Re-validate IDs after any document reload, sync, or undo.
- Process updates per-item so one stale ID doesn't discard the rest.
- Compare the ID's root_id with the document you believe you're editing.
When it happens
Trigger: Updating a block ID that the tree cache/load produced a tree for but that contains no matching node: stale ID from a deleted block, ID pointing into a different document, or a root/document ID passed to the block update API.
Common situations: Plugins holding editor state across document reloads; concurrent deletion via another window/device; automation updating lists of IDs captured from an outdated query.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- block not found [ ]
- Conf.Language(15) formatted with block id (localized…
- found invalid ID [ ]
- invalid block ID [ ]
- block not found
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/ecf09791c3c95326.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/block_update.go:125
oldTree = treeCache[cacheKey]
}
if nil == oldTree {
var loadErr error
oldTree, loadErr = loadTree(input.ID)
if loadErr != nil {
return nil, nil, fmt.Errorf("load block tree [%s] failed: %w", input.ID, loadErr)
}
if nil == oldTree || nil == oldTree.Root {
return nil, nil, fmt.Errorf("load block tree [%s] failed: tree is empty", input.ID)
}
treeCache[blockUpdateTreeKey{boxID: oldTree.Box, rootID: oldTree.ID}] = oldTree
if hasCacheKey {
treeCache[cacheKey] = oldTree
}
}
oldNode := treenode.GetNodeInTree(oldTree, input.ID)
if nil == oldNode {
return nil, nil, fmt.Errorf("block [%s] not found", input.ID)
}
if _, ok := rootIDSet[oldTree.ID]; !ok {
rootIDSet[oldTree.ID] = struct{}{}
rootIDs = append(rootIDs, oldTree.ID)
}
if ast.NodeDocument == oldNode.Type {
if validateErr := treenode.ValidateBlockSubtree(dataTree.Root); validateErr != nil {
return nil, nil, validateErr
}
for n := oldTree.Root.FirstChild; nil != n; n = n.Next {
if !n.IsBlock() || ast.NodeKramdownBlockIAL == n.Type {
continue
}
operations = append(operations, &Operation{Action: "delete", ID: n.ID, Data: map[string]any{
"createEmptyParagraph": false, // 清空文档后前端不要创建空段落
}})View on GitHub (pinned to 9f775e8a12)