{"record":{"id":"eb6d44665ab51f98","repo":"siyuan-note/siyuan","slug":"block-id-must-be-text","errorCode":null,"errorMessage":"block ID must be text","messagePattern":"block ID must be text","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/block_transaction.go","lineNumber":94,"sourceCode":"\tdata = bytes.TrimSpace(data)\n\t*r = BlockOperationResult{}\n\tif bytes.Equal(data, []byte(\"null\")) {\n\t\treturn nil\n\t}\n\tif len(data) > 0 && data[0] == '\"' {\n\t\treturn json.Unmarshal(data, &r.text)\n\t}\n\tvar values []json.RawMessage\n\tif len(data) == 0 || data[0] != '[' {\n\t\treturn fmt.Errorf(\"block operation result must be text, block IDs or null\")\n\t}\n\tif err := json.Unmarshal(data, &values); err != nil {\n\t\treturn err\n\t}\n\tr.ids = make([]string, len(values))\n\tfor i, value := range values {\n\t\tif bytes.Equal(bytes.TrimSpace(value), []byte(\"null\")) {\n\t\t\treturn fmt.Errorf(\"block ID must be text\")\n\t\t}\n\t\tif err := json.Unmarshal(value, &r.ids[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\ntype BlockOperation struct {\n\tAction            string               `json:\"action\" api:\"enum=delete|insert|update|foldHeading|unfoldHeading|setAttrs|moveOutlineHeading|appendInsert|prependInsert\"`\n\tData              BlockOperationData   `json:\"data\"`\n\tID                string               `json:\"id\"`\n\tRootID            string               `json:\"rootID\"`\n\tParentID          string               `json:\"parentID\"`\n\tPreviousID        string               `json:\"previousID\"`\n\tNextID            string               `json:\"nextID\"`\n\tRetData           BlockOperationResult `json:\"retData\"`\n\tBlockIDs          []string             `json:\"blockIDs\"`","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/block_transaction.go#L76-L112","documentation":"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.","triggerScenarios":"Decoding [\"20240101120000-abc\", null] or an array containing a number/bool/object element; whitespace-wrapped null (TrimSpace check) is also caught explicitly.","commonSituations":"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.","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"],"exampleFix":"// before\n{\"result\": [\"20240101120000-abc\", null]}\n// after\n{\"result\": [\"20240101120000-abc\", \"20240101120000-def\"]}","handlingStrategy":"validation","validationCode":"const ids = raw.filter(x => x !== null && x !== undefined);\nif (!ids.every(x => typeof x === \"string\" && x.length > 0)) throw new Error(\"block ID must be text\");","typeGuard":"function isBlockIdArray(v) { return Array.isArray(v) && v.every(x => typeof x === \"string\"); }","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["json","unmarshal","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}