siyuan-note/siyuan · error

block is not a task list item

Error message

block is not a task list item

What it means

The updateTaskListItemMarker endpoint only operates on task list items (list nodes with ListData.Typ == 3). buildUpdatedTaskListItemBlockDOM found the node in the tree, but it is a regular ordered/bullet list item, not a task item, so it refuses to apply a checkbox marker. This is a type guard, not a state problem.

Solutions

  1. Filter your block query to task list items only, e.g. subtype='t' on l blocks (SELECT id FROM blocks WHERE type='l' AND subtype='t')
  2. Check the returned block type/subtype with /api/query/sql before calling the marker API
  3. Convert the target to a task list item in the editor first, or use a generic block update operation instead of the marker-specific endpoint
  4. If you need to toggle markers in bulk, verify each item's subtype in the batch loop and skip non-task items

Example fix

// before
const blocks = await sql("SELECT id FROM blocks WHERE type='l' AND content LIKE '%TODO%'");
// after
const blocks = await sql("SELECT id FROM blocks WHERE type='l' AND subtype='t' AND content LIKE '%TODO%'");
Defensive patterns

Strategy: validation

Validate before calling

const rows = await fetchPost('/api/query/sql', { stmt: `SELECT subtype FROM blocks WHERE id='${id}'` });
if (!rows.length || rows[0].subtype !== 't') throw new Error(`block ${id} is not a task list item`);

Type guard

const isTaskListItem = (b) => !!b && b.type === 'l' && b.subtype === 't';

Try / catch

try {
  return await fetchPost('/api/block/updateTaskListItemMarker', { id, marker });
} catch (e) {
  if (typeof e?.msg === 'string' && e.msg.includes('not a task list item')) {
    console.warn(`Skipping non-task list item ${id}`);
    return null; // skip, don't crash the batch
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing the ID of a bullet list item (ListData.Typ == 0), an ordered list item (Typ == 1), or any non-list node (paragraph, heading, etc.) to POST /api/block/updateTaskListItemMarker or batchUpdateTaskListItemMarker.

Common situations: A plugin queries blocks WHERE type='l' but the matching item is a plain bullet list; UI selection grabbed the wrong node; content was converted from task item to bullet item by an earlier edit so a cached ID now points at a non-task item.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at kernel/api/block_op.go:55

		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")
	}

	liMarker := marker[0]
	if '[' == liMarker || ']' == liMarker {
		return "", errors.New("task list item marker can not be [ or ]")
	}

	markerNode := li.ChildByType(ast.NodeTaskListItemMarker)
	if nil == markerNode {
		return "", errors.New("task list item marker not found")
	}

	markerNode.TaskListItemMarker = liMarker
	markerNode.TaskListItemChecked = ' ' != markerNode.TaskListItemMarker

View on GitHub (pinned to 9f775e8a12)