siyuan-note/siyuan · error

block ID must be text

Error message

block ID must be text

What it means

Inside the array-form block operation result, each element must be a JSON string block ID. A literal null element is explicitly rejected with this message before the per-element unmarshal, and any non-string value fails on the inner json.Unmarshal into a string. This guarantees the ids slice contains only valid strings.

Solutions

  1. Replace any null array element with a valid block-ID string
  2. Filter out null entries before serializing the ID array on the producer side
  3. Fix the producer so failed operations emit an error rather than a null placeholder
  4. Validate each element is a non-empty string before sending

Example fix

// before
{"result": ["20240101120000-abc", null]}
// after
{"result": ["20240101120000-abc", "20240101120000-def"]}
Defensive patterns

Strategy: validation

Validate before calling

const ids = raw.filter(x => x !== null && x !== undefined);
if (!ids.every(x => typeof x === "string" && x.length > 0)) throw new Error("block ID must be text");

Type guard

function isBlockIdArray(v) { return Array.isArray(v) && v.every(x => typeof x === "string"); }

Try / catch

try { r = decodeIds(raw); } catch (e) { if (String(e).includes("block ID must be text")) { r = raw.filter(x => typeof x === "string"); } else throw e; }

Prevention

When it happens

Trigger: Decoding ["20240101120000-abc", null] or an array containing a number/bool/object element; whitespace-wrapped null (TrimSpace check) is also caught explicitly.

Common situations: Serializers that emit null for missing values in arrays; generic list coercion from dynamic languages where None/None-like values leak into ID lists; partial results where a failed operation contributed a null placeholder.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/eb6d44665ab51f98. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/block_transaction.go:94

	data = bytes.TrimSpace(data)
	*r = BlockOperationResult{}
	if bytes.Equal(data, []byte("null")) {
		return nil
	}
	if len(data) > 0 && data[0] == '"' {
		return json.Unmarshal(data, &r.text)
	}
	var values []json.RawMessage
	if len(data) == 0 || data[0] != '[' {
		return fmt.Errorf("block operation result must be text, block IDs or null")
	}
	if err := json.Unmarshal(data, &values); err != nil {
		return err
	}
	r.ids = make([]string, len(values))
	for i, value := range values {
		if bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
			return fmt.Errorf("block ID must be text")
		}
		if err := json.Unmarshal(value, &r.ids[i]); err != nil {
			return err
		}
	}
	return nil
}

type BlockOperation struct {
	Action            string               `json:"action" api:"enum=delete|insert|update|foldHeading|unfoldHeading|setAttrs|moveOutlineHeading|appendInsert|prependInsert"`
	Data              BlockOperationData   `json:"data"`
	ID                string               `json:"id"`
	RootID            string               `json:"rootID"`
	ParentID          string               `json:"parentID"`
	PreviousID        string               `json:"previousID"`
	NextID            string               `json:"nextID"`
	RetData           BlockOperationResult `json:"retData"`
	BlockIDs          []string             `json:"blockIDs"`

View on GitHub (pinned to 9f775e8a12)