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

  1. Run ast.IsNodeIDPattern(id) on every input before calling the API and drop invalid entries.
  2. Trim whitespace and verify the ID length matches the expected block ID length.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/ca77a9ce1862fcd4. Report an issue: GitHub.