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
- Replace any null array element with a valid block-ID string
- Filter out null entries before serializing the ID array on the producer side
- Fix the producer so failed operations emit an error rather than a null placeholder
- 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
- Filter null/undefined out of ID arrays before serialization
- Type the ID list as string[] and enforce it at build time
- Ensure failed operations return errors, not null placeholders
- Validate array elements before sending over the wire
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
- block operation result must be text, block IDs or null
- SQL value must be a JSON scalar
- createDocTree definition must be a list
- createDocTree document must be a dictionary
- Field [id] should be of type [String]
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)