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

  1. Re-fetch current block IDs (/api/query/sql or /api/block/getBlockInfo) and retry only with existing IDs.
  2. 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.
  3. Catch this error per batch item and skip/queue rather than aborting unrelated updates.
  4. 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

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


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)