siyuan-note/siyuan · error

setting or removing [data-task] attribute is not allowed via

Error message

setting or removing [data-task] attribute is not allowed via this interface. Please use "/api/block/updateTaskListItemMarker" or "/api/block/batchUpdateTaskListItemMarker" to update the task list item marker

What it means

In setNodeAttrs0, after the attr-name check, if lowerName == 'data-task' the function hard-rejects with a message directing the caller to /api/block/updateTaskListItemMarker or /api/block/batchUpdateTaskListItemMarker. The data-task attribute encodes task-list-item state; setting it directly via the generic attr API would desync the list-item marker from the AST, so the kernel funnels all such changes through the dedicated marker endpoints.

Source

Thrown at kernel/model/blockial.go:364

	newAttrsUnEsc := parse.IAL2MapUnEsc(node.KramdownIAL)
	for name := range nameValues {
		if "fold" == strings.ToLower(name) {
			delete(newAttrsUnEsc, "heading-fold")
			break
		}
	}

	for name, value := range nameValues {
		value = util.RemoveInvalidRetainCtrl(value)
		value = strings.TrimSpace(value)
		lowerName := strings.ToLower(name)
		// 转换为小写再验证属性名
		if !isValidAttrName(lowerName) {
			err = errors.New(Conf.Language(25) + " [" + node.ID + "]")
			return
		}
		if lowerName == "data-task" {
			err = errors.New(`setting or removing [data-task] attribute is not allowed via this interface. Please use "/api/block/updateTaskListItemMarker" or "/api/block/batchUpdateTaskListItemMarker" to update the task list item marker`)
			return
		}

		// 处理文档标签 https://github.com/siyuan-note/siyuan/issues/13311
		if lowerName == "tags" {
			var tags []string
			tmp := strings.SplitSeq(value, ",")
			for t := range tmp {
				t = strings.TrimSpace(t)
				if "" != t {
					tags = append(tags, t)
				}
			}
			tags = gulu.Str.RemoveDuplicatedElem(tags)
			if 0 < len(tags) {
				value = strings.Join(tags, ",")
			} else {
				value = ""

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Use POST /api/block/updateTaskListItemMarker {id, checked} for a single item, or /api/block/batchUpdateTaskListItemMarker for many.
  2. Filter 'data-task' out of any generic attribute map before calling setBlockAttrs.
  3. When copying block IAL wholesale, drop 'data-task' and re-apply it through the marker API.

Example fix

// before
fetchPost('/api/attr/setBlockAttrs', {id, attrs: {'data-task': 'todo'}})

// after: use the dedicated task-list marker endpoint
fetchPost('/api/block/updateTaskListItemMarker', {id, checked: false})
Defensive patterns

Strategy: validation

Validate before calling

// Never let data-task reach the generic attr API.
delete(nameValues, "data-task")
if taskWanted {
    model.UpdateTaskListItemMarker(id, checked) // dedicated endpoint
}

Try / catch

// HTTP caller: on the explicit data-task rejection, call the marker API instead.
if (r.code === -1 && r.msg.includes('data-task')) {
    await fetchSyncPost('/api/block/updateTaskListItemMarker', {id, checked: value === 'todo'})
}

Prevention

When it happens

Trigger: POST /api/attr/setBlockAttrs (or batchSetBlockAttrs, or MCP attr tool) with key 'data-task' on a task-list-item block, regardless of value (set or delete).

Common situations: A plugin toggling a checkbox by writing the IAL directly instead of using the marker API; generic 'set any attribute' tooling that does not special-case task state; copy of one block's full IAL onto another.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/d854e9d75e20021d. Report an issue: GitHub.