siyuan-note/siyuan · error
block is not a list item
Error message
block is not a list item
What it means
Thrown by buildUpdatedTaskListItemBlockDOM (kernel/api/block_op.go) when the block loaded from the index has a Type other than "NodeListItem". The task-marker update endpoint only operates on list items; passing any other block type (paragraph, heading, code block, blockquote, embed...) is rejected before the tree is even loaded.
Source
Thrown at kernel/api/block_op.go:56
util.BindJsonArg("dataType", &input.DataType, true, true),
util.BindJsonArg("lockType", &input.LockType, false, false),
) {
return
}
if util.InvalidIDPattern(input.ID, ret) {
return input, false
}
return input, true
}
func buildUpdatedTaskListItemBlockDOM(id, marker string, luteEngine *lute.Lute) (data string, err error) {
block, err := model.GetBlock(id, nil)
if err != nil {
return "", errors.New("get block failed: " + err.Error())
}
if "NodeListItem" != block.Type {
return "", errors.New("block is not a list item")
}
tree, err := filesys.LoadTree(block.Box, block.Path, luteEngine)
if err != nil {
return "", errors.New("load tree failed: " + err.Error())
}
li := treenode.GetNodeInTree(tree, id)
if li == nil {
return "", errors.New("block not found")
}
if 3 != li.ListData.Typ {
return "", errors.New("block is not a task list item")
}
if 1 != len(marker) {
return "", errors.New("task list item marker length should be 1")View on GitHub (pinned to afa823b6b4)
Solutions
- Fetch the block first (/api/block/getBlockInfo) and check its type equals NodeListItem before calling
- Target the specific list item block id, not its parent list or sibling content
- Filter candidate ids client-side so only NodeListItem blocks are sent
- If you need task semantics on another block type, convert it to a task list item in the editor first, then call
Example fix
// before
fetchPost('/api/block/updateTaskListItemMarker', { id: paragraphId, marker: 'x' }); // "block is not a list item"
// after
const info = await fetchPost('/api/block/getBlockInfo', { id });
if (info.data.type === 'NodeListItem') {
fetchPost('/api/block/updateTaskListItemMarker', { id, marker: 'x' });
} Defensive patterns
Strategy: type-guard
Validate before calling
const info = await fetchPost('/api/block/getBlockInfo', { id });
if (info.data?.type !== 'NodeListItem') {
throw new Error(`block [${id}] is ${info.data?.type}, not a list item`);
}
await fetchPost('/api/block/updateTaskListItemMarker', { id, marker }); Type guard
function isListItemBlock(b: { type: string } | null | undefined): b is { type: 'NodeListItem' } {
return b?.type === 'NodeListItem';
} Try / catch
if (e.message === 'block is not a list item') { /* re-select the target: use the list item's own id, not a parent/sibling */ } Prevention
- Map DOM elements to their own block id, never the enclosing block
- Re-fetch block info when a user selection may have changed block types
- Filter batch id lists by type before sending
When it happens
Trigger: POST /api/block/updateTaskListItemMarker with the id of a paragraph, heading, or any non-list block; passing a container block id (list itself) instead of the individual list item id; frontend gutter/checkbox handler wired to the wrong block after a DOM-to-id mapping mistake.
Common situations: A plugin toggles a checkbox it located by walking the DOM and grabs the parent paragraph; the block was converted from a list item to another type after the id was captured; batch updates iterate over a selection that includes non-list blocks.
Related errors
- block is not a task list item
- task list item marker length should be 1
- task list item marker can not be [ or ]
- block updates are empty
- get block failed: %s
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/289580e315e1092e.
Report an issue: GitHub.