siyuan-note/siyuan · error

Conf.Language(25) + " [" + node.ID + "]"

Error message

Conf.Language(25) + " [" + node.ID + "]"

What it means

setNodeAttrs0 validates each attribute name (lower-cased) with isValidAttrName; a name that is not lowercase letters/digits/hyphens and not starting with a lowercase letter produces Conf.Language(25): 'The attribute name can only contain lowercase English letters, digits, and hyphens, and must start with a lowercase English letter', suffixed with ' [' + node.ID + ']'. This enforces the IAL attribute-name grammar of the Kramdown storage format.

Solutions

  1. Normalize the attribute name: lowercase it, replace underscores/spaces with hyphens, strip illegal characters
  2. Prefix custom attributes with 'custom-' (convention for user-defined keys) and keep the rest [a-z0-9-]
  3. Reject or rename invalid keys in the caller before sending the request
  4. Map external metadata (e.g. YAML front matter keys) through a sanitizer function

Example fix

// before
await fetchPost('/api/attr/setBlockAttrs', {id, attrs: {'myAttr': '1'}});
// after
const safe = k => 'custom-' + k.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+/, '');
await fetchPost('/api/attr/setBlockAttrs', {id, attrs: {[safe('myAttr')]: '1'}});
Defensive patterns

Strategy: validation

Validate before calling

function isValidAttrName(name) { return /^[a-z][a-z0-9-]*$/.test(name); }

Type guard

null

Try / catch

try { await setBlockAttrs(id, attrs); } catch (e) { if (/attribute name/i.test(String(e))) sanitizeAndRetry(); else throw e; }

Prevention

When it happens

Trigger: POST /api/attr/setBlockAttrs or batchSetBlockAttrs (and internal setNodeAttrs* callers like updateBoundBlockAvsAttribute) with an attribute key containing uppercase letters, spaces, underscores, Chinese characters, or starting with a digit/hyphen.

Common situations: Plugins generating attr names from user input or CamelCase identifiers (e.g. 'myAttr', 'my_attr'); converting external data keys directly into SiYuan attrs; typos like 'Title'.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/blockial.go:390

			}
		}
	}
	oldAttrs = parse.IAL2Map(node.KramdownIAL)
	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
		}
		if DocSortModeAttr == lowerName {
			if ast.NodeDocument != node.Type || IsBoxDoc(boxID, node.ID) {
				err = fmt.Errorf("attribute [%s] is only supported on regular document roots", DocSortModeAttr)
				return
			}
			if "" != value {
				sortMode, parseErr := strconv.Atoi(value)
				if nil != parseErr || !IsValidDocSortMode(sortMode) {
					err = fmt.Errorf("invalid document sort mode [%s]", value)
					return
				}
				value = strconv.Itoa(sortMode)

View on GitHub (pinned to 9f775e8a12)